JWT (JSON Web Token) 攻击
1. 定义
JWT (JSON Web Token) 是一种紧凑的、URL 安全的令牌格式,用于认证和信息交换。JWT 攻击利用应用程序在创建、验证或处理这些令牌时的弱点。
JWT 由三部分组成:header.payload.signature
常见的 JWT 漏洞允许攻击者:
- 在不知道密钥的情况下伪造有效令牌
- 通过修改令牌声明来提升权限
- 完全绕过认证
- 冒充其他用户
2. 技术原理
JWT 结构:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. <- 头部 (Base64)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ. <- 载荷 (Base64)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c <- 签名头部: 指定算法(如 HS256、RS256、none) 载荷: 包含声明(用户 ID、角色、过期时间) 签名: 验证令牌未被篡改
常见的 JWT 攻击类型:
算法混淆 (alg=none): 某些库接受
"alg": "none",允许未签名的令牌。算法切换 (RS256 到 HS256): 欺骗服务器使用公钥作为 HMAC 密钥。
弱密钥暴力破解: 离线破解弱 HMAC 密钥。
JWT 头部注入 (jku/x5u): 指向攻击者控制的密钥服务器。
声明篡改: 在没有正确签名验证的情况下修改载荷。
3. 攻击流程(算法混淆)
sequenceDiagram
participant Attacker as 攻击者
participant Server as Web 服务器
participant Library as JWT 库
Note over Server: 服务器使用 RS256<br/>公钥/私钥对
Attacker->>Attacker: 获取服务器公钥
Attacker->>Attacker: 创建伪造的 JWT<br/>头部: alg=HS256<br/>载荷: admin=true
Attacker->>Attacker: 用公钥签名<br/>将其视为 HMAC 密钥
Attacker->>Server: 使用伪造的 JWT 请求
Server->>Library: 验证 JWT
Library->>Library: 从头部读取 alg=HS256<br/>使用公钥作为 HMAC 密钥<br/>签名匹配!
Library-->>Server: 令牌有效
Server-->>Attacker: 以管理员身份授予访问权限4. 真实案例:Auth0 算法混淆 (2015)
目标: Auth0 JWT 库用户。 漏洞类别: 算法混淆 (CVE-2015-9235)。
漏洞背景: Auth0 的 Node.js jsonwebtoken 库存在严重缺陷。在验证使用 RSA (RS256) 签名的令牌时,如果攻击者将头部中的算法更改为 HS256,库会使用 RSA 公钥作为 HMAC 密钥。
为什么会这样:
- RS256 使用非对称加密:私钥签名,公钥验证。
- HS256 使用对称加密:签名和验证使用相同的密钥。
- 公钥通常是…公开的(在 JWKS 端点、证书等中)。
- 库信任来自令牌本身的
alg头部。
攻击过程:
// 原始令牌 (RS256)
// 头部: {"alg": "RS256", "typ": "JWT"}
// 使用服务器私钥签名
// 攻击者伪造的令牌 (HS256)
// 头部: {"alg": "HS256", "typ": "JWT"}
// 载荷: {"user": "admin", "role": "superuser"}
// 使用服务器公钥作为 HMAC 密钥签名影响: 任何使用该易受攻击库的应用程序都可能完全绕过其认证。这影响了数千个使用 Auth0 库的应用程序。
5. 深度防御策略
A. 显式指定算法
永远不要信任令牌头部中指定的算法。
// 坏:来自令牌头部的算法
jwt.verify(token, secret);
// 好:显式算法白名单
jwt.verify(token, secret, { algorithms: ['HS256'] });
// 好:对于 RSA
jwt.verify(token, publicKey, { algorithms: ['RS256'] });B. 强密钥
对于基于 HMAC 的算法,使用加密强度的密钥。
- 最小长度: HS256 需要 256 位(32 字节)。
- 随机生成: 使用加密随机生成器。
- 避免字典词: 避免可猜测的密码。
# 生成强密钥
openssl rand -base64 32弱密钥可以被离线破解:
# 攻击者可以暴力破解弱密钥
hashcat -m 16500 jwt.txt wordlist.txtC. 适当的密钥管理
- 分离密钥: 不同环境使用不同密钥。
- 密钥轮换: 定期轮换并保留重叠期。
- 安全存储: 使用 HSM 或密钥管理服务。
D. 验证所有声明
不仅要验证签名——还要验证载荷。
jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'https://myapp.com', // 验证发行者
audience: 'https://api.myapp.com', // 验证受众
clockTolerance: 30, // 允许 30 秒时钟偏差
maxAge: '1h' // 拒绝超过 1 小时的令牌
});E. 短过期时间
最小化被盗令牌的机会窗口。
- 访问令牌: 15 分钟或更短。
- 刷新令牌: 数小时到数天,需安全存储。
- 实现令牌撤销: 为受损令牌维护黑名单。
F. 避免在载荷中放置敏感数据
JWT 是签名的,不是加密的(除非使用 JWE)。
// 坏:JWT 中的敏感数据
{
"user_id": 123,
"credit_card": "4111-1111-1111-1111", // 永远不要这样做!
"ssn": "123-45-6789" // 永远不要这样做!
}
// 好:仅非敏感标识符
{
"user_id": 123,
"role": "user",
"exp": 1609459200
}G. 分布式系统使用非对称算法
当多个服务需要验证令牌时:
- RS256/RS384/RS512: RSA 签名
- ES256/ES384/ES512: ECDSA 签名
只有认证服务器拥有私钥;所有服务都可以使用公钥验证。
H. 实现 JTI (JWT ID) 防止重放
{
"jti": "unique-token-id-abc123", // 唯一标识符
"user_id": 123,
"exp": 1609459200
}跟踪已使用的 jti 值以防止令牌重放。
