CISCN2019 - Web - Double Secret

CISCN2019 - Web - Double Secret
Hunter题目信息
| 项 | 内容 |
|---|---|
| 比赛 | CISCN2019 华东南赛区 |
| 方向 | Web |
| 难度 | 中等(源码泄露 + 编码绕过) |
| 环境 | 授权靶场(已脱敏) |
| 考点 | RC4 加密、Flask SSTI、Werkzeug 调试器、Python2 编码 |
信息收集
robots.txt泄露/static/secretkey.txt,内容为you-will-never-guess(干扰项,真实密钥不在此)。- 盲猜
/secret路由成功,返回Tell me your secret.I will encrypt it so others can't see。
漏洞分析
定位注入点与源码
传入非法字符触发 Werkzeug debugger(Python 2.7 环境,路径 /usr/local/lib/python2.7/),泄露 /app/app.py 的 secret 函数:
1 | if(secret==None): |
并泄露 /app/rc4_Modified.py 的 do_crypt:
1 | r = (self.Box[self.index_i] + self.Box[self.index_j]) % 256 |
可见 render_template_string(safe(deS)) 把解密结果当模板渲染,存在 SSTI。
确认 RC4 密钥
真正密钥是 HereIsTreasure(不是 secretkey.txt 的 you-will-never-guess)。通过单字符映射验证:a→4,flag 的 RC4 密文 3\x03XR 与服务端一致。
核心难点:Python2 Werkzeug 的编码问题
直接发送 RC4 密文的高位字节(%80-%FF)会失败:服务端 do_crypt 里 tmp=ord(s)^R、out.append(chr(tmp)),而 Werkzeug 把 URL query 里的 %FD 按 UTF-8 解码,非法序列被替换成 U+FFFD,ord() 返回 65533,chr() 报 ValueError: chr() arg not in range(256)。
解法:把 RC4 密文字节按 latin-1 解码成 Unicode 字符,再按 UTF-8 编码 URL 发送。高位字节 0xFD 变成 %C3%BD(ý,码点 U+00FD=253),服务端 ord('ý') 返回 253,XOR 正确。
1 | cipher = rc4(key, payload.encode('utf-8')) # RC4 加密得 bytes |
验证:{{7*7}} 密文 2e140e1ffd1a16,发送后回显 49,SSTI 确认。
利用过程
- 触发 Werkzeug debugger 读出
app.py、rc4_Modified.py,拿到密钥HereIsTreasure。 - 本地实现 RC4,对 SSTI 载荷加密 → latin-1 解码 → UTF-8 URL 编码。
- 发送
{{7*7}}验证回显49。 - 发送读文件载荷:
1 | payload = "{{ config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read() }}" |
返回 'class' is not allowed. Secret is flag{xxxx}:class 被 WAF 过滤,但 cat /flag.txt 的输出中 flag 明文已随错误提示泄露,无需再绕过 WAF。
完整 EXP
1 | import http.client, urllib.parse |
Flag
flag{xxxx}(已脱敏)
踩坑与总结
- 密钥识别:
secretkey.txt的you-will-never-guess是干扰项,真实密钥HereIsTreasure硬编码在app.py。 - 编码绕过:Python2 Werkzeug 对 URL 高位字节按 UTF-8 解码导致
chr() arg not in range(256),需用 latin-1 解码 + UTF-8 编码的「双重编码」还原字节值。 - flag 泄露方式:WAF 拦
class,但 flag 明文出现在错误提示中,不必死磕绕过。 - 平台提交
accepted,二次复现一致。






