之前打铁三,看了别的师傅的wp之后发现go也有ssti,于是找了个go ssti题来学学
Dockerfile中flag是假的,所以直接审源码
有4个路由:
1 2 3 4 5 6 7 8 9 10
| func main() { admin := Account{admin_id, admin_pw, true, secret_key} acc = append(acc, admin)
http.HandleFunc("/", root_handler) http.HandleFunc("/auth", auth_handler) http.HandleFunc("/flag", flag_handler) http.HandleFunc("/regist", regist_handler) log.Fatal(http.ListenAndServe("0.0.0.0:11000", nil)) }
|
先审最开始的’/‘
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func root_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token") if token != "" { id, _ := jwt_decode(token) acc := get_account(id) tpl, err := template.New("").Parse("Logged in as " + acc.id) if err != nil { } tpl.Execute(w, &acc) } else {
return } }
|
先获取Token,如果有Token则用jwt解密,如果解密成功则显示用户的id,如果没有token直接返回空白
审’/auth’
1 2 3 4 5
| uid := r.FormValue("id") upw := r.FormValue("pw") if uid == "" || upw == "" { return }
|
获取用户传入的id和pw
1 2 3 4 5 6 7 8 9 10 11 12 13
| user_acc := get_account(uid) if user_acc.id != "" && user_acc.pw == upw { token, err := jwt_encode(user_acc.id, user_acc.is_admin) if err != nil { return } p := TokenResp{true, token} res, err := json.Marshal(p) if err != nil { } w.Write(res) return }
|
如果输入了正确的id和pw(get_account有匹配的正确结果),返回一个TokenResp的对象(json形式),里面存储了状态status,和jwt token,内容是id和是否为admin
1 2 3 4
| type TokenResp struct { Status bool `json:"status"` Token string `json:"token"` }
|
/flag路由解码jwt,is_admin为true给flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func flag_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token") if token != "" { id, is_admin := jwt_decode(token) if is_admin == true { p := Resp{true, "Hi " + id + ", flag is " + flag} res, err := json.Marshal(p) if err != nil { } w.Write(res) return } else { w.WriteHeader(http.StatusForbidden) return } } }
|
最后是/regist路由,路由中默认给is_admin为false,所以我们要想办法给is_admin为true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| func regist_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id") upw := r.FormValue("pw")
if uid == "" || upw == "" { return }
if get_account(uid).id != "" { w.WriteHeader(http.StatusForbidden) return } if len(acc) > 4 { clear_account() } new_acc := Account{uid, upw, false, secret_key} acc = append(acc, new_acc)
p := Resp{true, ""} res, err := json.Marshal(p) if err != nil { } w.Write(res) return }
|
现在,我们的思路是:用go ssti注入获取key然后伪造jwt
先说一下go的ssti,和jinja2的ssti类似,都是因为直接渲染拼接的字符导致插入了模板语言后执行
Go 语言内置了 text/template 和 html/template 两个模板库。如果开发人员没有正确使用这些库,可能会导致 SSTI 注入。例如,如果使用 text/template 处理用户输入,并且未对输入进行转义,攻击者可以插入恶意模板代码。
1
| 个模板语句的作用是在模板渲染中输出当前上下文中的数据。如果传递给模板引擎的是一个结构体类型的变量,使用“{{.}}”将会引用整个结构体的数据。而如果您想引用结构体中的某个字段,可以使用“{{.FieldName}}”来引用。
|
1 2 3 4 5 6
| type Account struct { id string pw string is_admin bool secret_key string }
|
这里我们要用
来显示整个结构体来获取到secret_key
第一步,访问regist注册一个id为
的账户,因为存在ssti漏洞,acc.id会被直接渲染
注册后,访问auth,获得token
在header中加入X-TOKEN访问’/‘路由,查询到key
去jwt.io,伪造token,输入key,把is_admin改为true然后带着token访问/即可拿到flag