deependra-painkra/JWT-Algorithm-Confusion-Verification
GitHub: deependra-painkra/JWT-Algorithm-Confusion-Verification
一个针对 JWT 算法混淆漏洞修复验证的自动化安全测试框架,集成了威胁建模与多种攻击载荷模拟。
Stars: 0 | Forks: 0
# JWT-Algorithm-Confusion-验证
全面的 JWT 算法混淆修复验证框架,包含威胁建模、测试设计和可运行的实现
# A 部分:威胁建模 - JWT 算法混淆攻击
## 1. 算法混淆攻击解释
### 什么是算法混淆?
算法混淆是一类漏洞,即 JWT 验证逻辑接受多种签名算法,并可能被诱骗接受使用非预期算法签名的 token。
### 核心漏洞机制
原始服务器代码基于此**关键假设**运行:
```
# 脆弱代码模式
import jwt
public_key = load_public_key_pem() # Intentionally public
def verify_token(token):
decoded = jwt.decode(
token,
public_key, # Single key for all algorithms
algorithms=["RS256", "HS256"] # BUG: Accept both
)
return decoded
```
该假设是**可被利用的**,因为:
#### 1. **非对称到对称降级**
- 服务器被配置为验证 RS256 token(RSA with SHA-256),这需要私钥进行签名,需要公钥进行验证。
- 然而,代码也接受了 HS256 token(HMAC with SHA-256),后者使用共享密钥。
#### 2. **公钥作为密钥**
- 攻击者获取了服务器的公钥(该密钥本质上是公开的),并将其用作 HS256 算法中的 HMAC 密钥。
- 这之所以可行,是因为:
- RS256 验证:`VERIFY(token, public_key_RS256)`
- HS256 验证:`VERIFY(token, shared_secret_HS256)`
- 如果 `public_key == shared_secret`,则相同的密钥材料执行了这两种操作。
#### 3. **攻击执行**
- 攻击者:
- 获取了一个合法的管理员 API token 载荷(例如 `{"sub": "user123", "role": "user"}`)
- 将头部修改为 `{"alg": "HS256", "typ": "JWT"}`
- 使用服务器的公钥作为密钥,通过 HMAC-SHA256 对其进行签名
- 将其发送到服务器,服务器错误地信任了 `alg` 头部并使用 HS256 逻辑进行验证
### 为什么这会奏效
易受攻击的代码可能遵循以下模式:
```
# 脆弱代码模式
import jwt
public_key = load_public_key_pem() # Intentionally public
def verify_token(token):
decoded = jwt.decode(
token,
public_key, # Single key for all algorithms
algorithms=["RS256", "HS256"] # BUG: Accept both
)
return decoded
```
### 关键缺陷
当库在头部看到 `"alg": "HS256"` 时,它将 `public_key` 视为 HMAC 密钥,而不是 RSA 公钥。攻击者现在可以在没有私钥的情况下伪造有效的 token。
**命名攻击向量**
* **密钥混淆向量:** 混淆密钥用途(公钥 ↔ 共享密钥)
* **算法覆盖向量:** 在未严格强制的情况下信任不可信的 `alg` 头部
* **降级攻击:** 强制从更强的方案 (RSA) 转移到更弱的方案 (HMAC)
* **HMAC 伪造:** 在不知道私钥的情况下创建有效的 HMAC 签名
* **签名算法替换:** 用攻击者控制的变体替换预期算法
**原始漏洞根本原因**
服务器犯了三个关键错误:
1. **信任 `alg` 头部:** JWT 头部是用户可控的,不应决定算法行为。
2. **多算法支持:** 在没有显式密钥绑定到算法的情况下接受 `RS256` 和 `HS256`。
3. **无算法验证:** 未能验证 token 的算法是否与预期算法匹配。
### 2. 客户声称的修复分析
**客户声称**
**这应该意味着什么**
客户应该实现如下结构:
```
# 修复代码模式
def verify_token(token):
decoded = jwt.decode(
token,
public_key,
algorithms=["RS256"] # ONLY RS256
)
return decoded
```
**为什么这能缓解(但不能完全保证)**
* ✅ 防止 `HS256` 伪造(算法必须匹配)
* ✅ 防止 `alg: none` 攻击(不在允许列表中)
* ✅ 防止算法降级
* ⚠️ **但是:** 取决于正确的实现、库版本和配置。
**有效修复的关键依赖项**
修复仅在以下所有条件都为真时才有效:
* **硬编码算法列表:** `algorithms=["RS256"]` 是硬编码的,而不是从动态配置加载的。
* **无回退路径:** 零 try-except 块在验证失败时静默接受 token。
* **单一验证路径:** 所有 token 验证都通过固定函数进行。
* **库版本:** JWT 库版本已针对算法混淆打补丁(例如 PyJWT ≥ 2.0)。
* **无缓存绕过:** Token 未被缓存或预验证,从而绕过严格检查。
**剩余风险面**
即使“仅强制执行 RS256”,如果出现以下情况,修复仍可能失败:
* **多代码路径漏洞:** 其他尚未更新的函数中存在 `HS256` 验证代码。
* **异常处理绕过:** `RS256` 验证失败,异常被捕获,并且 `HS256` 作为回退被静默接受。
* **配置覆盖:** 允许的算法列表从环境/配置加载,默认为 `["RS256", "HS256"]`。
* **库级错误:** 使用易受攻击的旧版本(例如 PyJWT ≤ 1.5.2, jsonwebtoken ≤ 8.3.0, 具有已知绕过方法的 golang-jwt)。
* **基于 KID 的混淆:** `kid` 头部在应选择 RSA 公钥时选择了对称密钥。
* **缓存层:** Token 带有 TTL 被缓存;攻击者制作与缓存条目匹配的 token。
* **头部操纵:** WAF、代理或中间件在到达服务器之前剥离了算法验证头部。
* **大小写敏感性:** 库将 `alg: rs256`(小写)与 `alg: RS256`(大写)区别对待。
### 3. 攻击前提条件与假设
**攻击者需要什么**
* **公钥:** 可从 `/.well-known/jwks.json`、证书文件或公共仓库(如 GitHub)获取。
* **算法接受度:** 知道服务器同时接受 `RS256` 和 `HS256`。
* **载荷访问权限:** 一个用于提取载荷的合法 token,或对目标载荷结构的了解。
* **HMAC 签名能力:** 能够在攻击者的本地机器上使用任何密钥签署 token 的能力。
**服务器(错误地)假设的内容**
* `alg` 头部本质上是安全关键的,应该被信任以决定验证逻辑。
* 在多种算法中使用相同的密钥是安全的。
* 在数学上验证签名即证明 token 是真实的(忽略了算法不匹配)。
* 因为 token 已成功签名,所以它一定是合法的。
* 在修补主要漏洞时,所有验证代码路径都已统一更新。
### 攻击流程图

### 4. 残余风险评估
在“强制执行 RS256”之后,验证:
**第 1 层:代码级验证**
* 在代码库中搜索所有 `jwt.decode()` 调用。
* 确认所有调用中都有 `algorithms=["RS256"]`。
* 确认 `algorithms` 不是从不可信来源参数化的。
* 确认没有捕获并回退的逻辑。
**第 2 层:库验证**
* 确认 PyJWT >= 2.0(或等效的安全补丁)。
* 确认 JWT 库版本中没有已知的 CVE。
* 针对依赖项运行 `pip check` 或 `cargo audit`。
**第 3 层:配置验证**
* 确认允许的算法不在配置文件中。
* 确认没有环境变量覆盖算法列表。
* 确认没有绕过验证的缓存。
**第 4 层:集成验证**
* 发送 `HS256` token → 期望 `401`
* 发送带有 `HS256` 签名的 `RS256` token → 期望 `401`
* 发送有效的 `RS256` token → 期望 `200` (Success)
* 发送 `RS256` 但使用小写 `alg: rs256` → 期望被拒绝或 `200`(取决于具体的库修复)。
# B 部分:最小可行测试套件设计
## 完整测试用例表
| 测试 ID | 类别 | Token 修改 | 预期 (易受攻击) | 预期 (已修复) | 通过条件 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **TC-01** | Algorithm None 攻击 | 设置 `alg: none`,移除签名 | `200 OK` + 返回管理员数据 | `401 Unauthorized` | 如果响应为 `401` 且未泄露管理员数据 |
| **TC-02** | 使用公钥的 HS256 (原始利用) | 更改 `alg: HS256`,使用公钥作为密钥签名,保留管理员载荷 | `200 OK` + 返回管理员用户列表 | `401 Unauthorized` | 如果响应为 `401` 且未暴露敏感数据 |
| **TC-03** | 算法头部移除 | 完全从头删除 `alg` 字段 | `200 OK` (假设默认) | `401` 或 `400 Bad Request` | 如果请求被拒绝或返回错误 |
| **TC-04** | KID 头部注入 | 注入 `kid: ../../../etc/passwd` 或通配符 `kid: *` | 可能绕过密钥查找 | `401` 或密钥未找到错误 | 如果请求被拒绝且未发生路径遍历 |
| **TC-05** | 过期 Token 接受 | 设置 `exp: 1609459200` (2021-01-01) | `200 OK` (无验证) | `401 Unauthorized` | 如果响应为 `401` 并带有 "token expired" 消息 |
| **TC-06** | 篡改载荷 - 角色升级 | 在载荷中将 `role: user` 更改为 `role: admin`,使用 HS256+pubkey 重新签名 | `200 OK` 拥有管理员权限 | `401 Unauthorized` | 如果响应为 `401` 且未发生权限升级 |
| **TC-07** | 空白/Null 算法 | 设置 `alg: ""` 或 `alg: null` | `200 OK` (视为 none) | `401` 或 `400 Bad Request` | 如果请求被拒绝 |
| **TC-08** | 密钥不匹配的 RS256 | 使用不同的 RSA 私钥签名,发送 RS256 token | `200 OK` (错误的密钥验证) | `401 Unauthorized` | 如果响应为 `401` (签名不匹配) |
| **TC-09** | RS256 强制执行验证 | 使用有效 RS256 token 和正确密钥,合法用户角色 | `200 OK` 返回用户数据 | `200 OK` 返回用户数据 (无权限升级) | 如果响应为 `200` 且载荷角色与 token 匹配 (无升级) |
| **TC-10** | PyJWT 库 CVE 绕过 | 使用已知的 PyJWT 版本绕过方法(例如算法大小写敏感性) | `200 OK` | `401 Unauthorized` | 如果响应为 `401` (库已修补) |
| **TC-11** | 大小写敏感性攻击 | 设置 `alg: rs256` (小写) 而不是 `RS256` | 可能在不区分大小写的库中成功 | `401 Unauthorized` | 如果响应为 `401` (区分大小写验证) |
| **TC-12** | 多算法值 | 设置 `alg: ["RS256", "HS256"]` (JSON 数组) | 可能接受第一个或任何一个 | `401 Unauthorized` | 如果请求被拒绝 |
## 测试执行策略
### 阶段 1:准备
1. 为服务器生成 RSA 密钥对(`server_public.pem`, `server_private.pem`)。
2. 为攻击者生成 RSA 密钥对(`attacker_private.pem`)。
3. 将公钥加载到测试工具中。
4. 创建基础 token(有效的 `RS256` token)。
### 阶段 2:测试执行
对于每个测试用例:
1. **生成修改后的 token** 使用策略。
2. **准备 HTTP 请求** 在 `Authorization` 头部中包含 token。
3. **发送请求** 到目标端点。
4. **捕获响应**:状态码、主体、头部、响应时间。
5. **分析响应**:敏感数据模式、时间异常。
6. **判定**:基于预期与实际响应的 PASS/FAIL。
### 阶段 3:分析
* 统计通过 vs 失败。
* 识别敏感数据泄露。
* 识别时间异常(>3 秒)。
* 确定总体判定:`REMEDIATION_SUCCESS` 或 `REMEDIATION_FAILED`。
## 敏感数据模式
如果匹配以下内容,验证脚本会将响应标记为“包含敏感数据”:
```
"users?":\s*\[ # JSON user array
"admin" # Admin keyword
"email":\s*" # Email field
"password":\s*" # Password field
"token":\s*" # Token field
user\d+@ # User email pattern
\$2[aby]\$ # bcrypt hash pattern
```
## 预期结果
### 如果修复成功
✅ **所有 12 项测试通过:**
* **TC-01 至 TC-08:** 全部返回 `401 Unauthorized`(恶意 token 被拒绝)。
* **TC-09:** 返回 `200 OK`(有效 token 被接受)。
* **TC-10, TC-11 TC-12:** 全部返回 `401 Unauthorized`(变体被拒绝)。
* 未检测到敏感数据泄露。
* 响应时间保持正常(< 3 秒)。
### 如果修复失败
❌ **至少 1 项测试失败:**
* TC-01 至 TC-08, TC-10, TC-11, TC-12 中的一个或多个返回 `200 OK`。
* **或者** 响应主体中出现敏感数据。
* **或者** 响应时间 > 3 秒。
* *结论:* 表明漏洞未完全修复,攻击面仍然开放。
## 实现说明
* 测试按顺序运行,请求之间有 **0.5s 延迟** 以遵守速率限制。
* **每个请求的超时时间:** 3 秒。
* 任何超时都会自动触发该特定测试用例的 **FAIL**。
* 生成包含时间戳和 SHA-256 哈希的全面 JSON 报告,用于完整性跟踪。
* 生成用于人工审查的 Markdown 报告。
* 所有工件和日志都保存到 `evidence/` 目录。
# C 部分:AI 辅助工作流与关键批评
## 1. 使用的 AI 提示词
### 提示词 1:绕过机制
你是一名 JWT 安全专家。鉴于服务器声称通过“强制执行严格的 RS256 算法验证”修复了算法混淆漏洞,请列出此修复仍可能不完整或被绕过的 5 种不同方式。对于每种方式,用一句话解释绕过机制,并对其进行分类(token 头部操纵、密钥混淆、库级错误、回退路径、缓存行为或其他)。
原始漏洞:服务器接受使用服务器公钥作为 HMAC 密钥的 HS256 签名 token。
关注现实世界的实现差距,而不是理论攻击。
### 提示词 2:置信度标准
哪 3 个可衡量、可测试的条件,如果全部为真,能让你高置信度地宣布 JWT 算法混淆修复成功?对于每个条件,具体说明你将如何通过编程或测试来验证它。
### 提示词 3:密钥轮换影响
一位客户说他们在 JWT 算法混淆漏洞后每 24 小时轮换一次 JWT 密钥以提高安全性。请评估:密钥轮换是否加强了对算法混淆的修复,还是无关紧要?请提供安全工程师的分析及其推理。
## 2. AI 原始输出(未编辑)
### 对提示词 1 的响应:5 种绕过机制
### 对提示词 2 的响应:3 个可衡量的成功条件
### 对提示词 3 的响应:密钥轮换与算法混淆
## 3. 对 AI 输出的关键批评
### AI 正确的部分
1. **绕过 #1(回退到 HS256)**:在真实代码中非常现实且常见。Try-except-accept 模式是一个典型的漏洞。
2. **绕过 #3(库漏洞)**:正确识别了库版本很重要。
- **具体问题**:PyJWT ≤ 1.5.2 (CVE-2017-11424), jsonwebtoken < 8.4.2, golang-jwt < 3.1.0
3. **绕过 #4(大小写敏感性)**:不区分大小写库中的真实问题。
4. **成功条件 #1(算法白名单)**:测试多种 `alg` 变体是合理的。
5. **成功条件 #2(签名验证)**:良好的测试设计(正向 + 负向用例)。
6. **密钥轮换分析**:正确识别出它与算法混淆无关。
### 初步分析中的关键缺陷
#### 1. 缺乏可操作的验证
未能提供具体的 `grep` 命令来查找配置覆盖,或具体的 Python 脚本(如使用 `ast` 模块)来审计 `jwt.decode` 调用。
#### 2. 不完整的攻击向量
未解释 `kid` 头部注入需要攻击者控制密钥库,并使用了模糊术语,而不是引用易受攻击 JWT 库的具体 CVE。
#### 3. 忽视基础设施与传输风险
忽略了外部绕过,例如代理/WAF 剥离授权头部、缓存层(如 Redis)在缓存命中时跳过验证,以及需要 TLS/HTTPS 来防止 MITM 攻击。
## 4. 改进与修正版本
### 算法混淆修复层级(正确的安全态势)
**必须有(阻塞问题):**
* 仅强制执行 `RS256`(而非 `HS256`)
* 拒绝 `alg: none`
* 拒绝未签名的 token
* 无回退路径
**应该有(深度防御):**
* 保持 JWT 库更新
* 每 1-2 年轮换一次 RSA 密钥对
* 使用较短的过期时间(15 分钟 - 1 小时)
* 始终验证 `exp` 声明
* 记录所有失败的验证尝试
**最好有(额外安全):**
* 实现 token 绑定 (RFC 8471)
* 使用双向 TLS 进行 token 传输
* 实现 JWT 刷新 token 模式
## 5. 用于实现的修正版本
以下是应在您的测试套件中使用的集成输出:
### 测试覆盖映射
```
BYPASS_MECHANISMS = {
'TC-01': {
'name': 'alg_none attack',
'bypass_mechanism': 'Fallback exception handler or alg: none acceptance',
'detection': 'TC-01 fails (returns 200)',
},
'TC-02': {
'name': 'HS256 with public key',
'bypass_mechanism': 'Configuration override or library version bug',
'detection': 'TC-02 fails (returns 200 + sensitive data)',
},
'TC-03': {
'name': 'alg header removal',
'bypass_mechanism': 'Default algorithm assumption',
'detection': 'TC-03 fails (returns 200)',
},
'TC-04': {
'name': 'KID injection',
'bypass_mechanism': 'Untrusted key selection via KID header',
'detection': 'TC-04 fails (key lookup succeeds for malicious KID)',
},
'TC-05': {
'name': 'Expired token',
'bypass_mechanism': 'Missing or bypassed expiration validation',
'detection': 'TC-05 fails (returns 200 with expired token)',
},
'TC-06': {
'name': 'Role escalation (HS256)',
'bypass_mechanism': 'HS256 acceptance + missing payload validation',
'detection': 'TC-06 fails (returns 200 with role=admin)',
},
'TC-07': {
'name': 'Blank algorithm',
'bypass_mechanism': 'Default algorithm assumption or alg: none fallback',
'detection': 'TC-07 fails (returns 200)',
},
'TC-08': {
'name': 'RS256 mismatched key',
'bypass_mechanism': 'Signature validation with wrong key (library bug)',
'detection': 'TC-08 fails (returns 200 with forged signature)',
},
'TC-09': {
'name': 'Valid RS256 (should pass)',
'bypass_mechanism': 'None (this is a positive test)',
'detection': 'TC-09 fails (returns non-200)',
},
'TC-10': {
'name': 'PyJWT library CVE',
'bypass_mechanism': 'Known CVE in PyJWT version (CVE-2017-11424 for ≤1.5.2)',
'detection': 'TC-10 fails (library version vulnerable)',
},
'TC-11': {
'name': 'Case sensitivity',
'bypass_mechanism': 'Case-insensitive library behavior vs. case-sensitive whitelist',
'detection': 'TC-11 fails (returns 200 with lowercase alg)',
},
'TC-12': {
'name': 'Multiple algorithms',
'bypass_mechanism': 'Array handling in JWT header parsing',
'detection': 'TC-12 fails (returns 200 with alg array)',
},
}
SUCCESS_CONDITIONS = {
'condition_1_algorithm_whitelist': {
'description': 'All invalid alg variants return 401',
'test_cases': ['TC-01', 'TC-03', 'TC-07', 'TC-11', 'TC-12'],
'verification': 'All listed TCs pass',
},
'condition_2_signature_validity': {
'description': 'Valid RS256 accepted, forged tokens rejected',
'test_cases': ['TC-09', 'TC-08'],
'verification': 'TC-09 returns 200, TC-08 returns 401',
},
'condition_3_no_bypass_paths': {
'description': 'No exception handlers, config overrides, or caching bypasses',
'test_cases': ['All TCs', 'Code review', 'Config audit'],
'verification': 'Zero findings in static/dynamic analysis',
},
}
SECRET_ROTATION_VERDICT = {
'question': 'Does 24-hour secret rotation strengthen the algorithm confusion fix?',
'answer': 'NO - It is irrelevant to algorithm confusion',
'reasoning': [
'Algorithm confusion depends on algorithm choice, not secret strength',
'Public key is publicly known and cannot be "rotated" for protection',
'Rotation would only help if combined with proper algorithm enforcement',
'The real fix is rejecting HS256, not rotating the public key',
],
'recommendation': 'Ask client: Do you reject HS256? If no, rotation is pointless.',
}
```
# D 部分:实现冲刺与执行
## 摘要
本节记录了 JWT 算法混淆验证套件的实现,包括提示词工程过程、AI 输出评估和生产级代码改进。
### 已实现的文件
| 文件 | 用途 | 行数 |
|------|---------|-------|
| `scripts/generate_test_keys.py` | RSA 密钥对生成 | 75 |
| `scripts/generate_test_tokens.py` | 测试 token 创建 | 145 |
| `scripts/verify_jwt.py` | 主验证工具 | 520 |
| `challenge2_config.json` | 配置 | 30 |
| `scripts/run_all_tests.sh` | 编排 | 45 |
## 关键实现细节
### 1. Token 生成策略
测试 token 生成器为每种攻击向量创建具有特定属性的 token:
| Token 类型 | 算法 | 密钥/Key | 载荷 | 用途 |
|------------|-----------|------------|---------|---------|
| `valid_rs256` | RS256 | server_private | user role, non-expired | 正向测试 (应成功) |
| `hs256_with_pubkey` | HS256 | server_public | user role, non-expired | 原始利用 (应失败) |
| `alg_none` | none | (empty) | user role, non-expired | 算法绕过 (应失败) |
| `expired` | RS256 | server_private | user role, EXPIRED | 过期绕过 (应失败) |
| `tampered_admin` | HS256 | server_public | admin role, non-expired | 权限升级 (应失败) |
| `attacker_key` | RS256 | attacker_private | user role, non-expired | 签名伪造 (应失败) |
### 2. 请求/响应分析
每个测试请求捕获:
```
{
'test_id': 'TC-01',
'strategy': 'alg_none',
'status_code': 401, # HTTP status
'response_time': 0.28, # seconds
'has_sensitive_data': False, # Pattern match
'result': 'PASS', # or 'FAIL'
'timestamp': '2026-03-18T10:42:00Z'
}
```
### 3. 敏感数据检测
使用正则表达式模式识别数据泄露:
```
SENSITIVE_PATTERNS = [
r'"users?":\s*\[', # user array
r'"admin"', # admin keyword
r'"email":\s*"', # email field
r'"password":\s*"', # password field
r'"token":\s*"', # token field
r'user\d+@', # email pattern
r'\$2[aby]\$', # bcrypt hash
]
```
### 4. 证据收集
所有测试运行生成带时间戳的证据:
```
evidence/
├── jwt_verification_report_*.md # Human-readable report
├── jwt_verification_results_*.json # Structured data
└── jwt_verification_results_*.sha256 # Integrity hash
```
SHA-256 哈希确保报告完整性以用于审计跟踪。
# 执行指南
## 快速开始
### 1. 安装依赖
```
pip install -r requirements.txt
```
### 2. 运行完整测试套件
```
bash scripts/run_all_tests.sh
```
### 3. 查看结果
```
cat evidence/jwt_verification_report_*.md
```
## 手动执行(分步)
### 1. 生成密钥
```
python3 scripts/generate_test_keys.py
```
输出:keys/server_public.pem, keys/server_private.pem, keys/attacker_private.pem
### 2. 生成 token
```
python3 scripts/generate_test_tokens.py
```
输出:keys/test_tokens.json (包含所有变体)
### 3. 运行验证
```
python3 scripts/verify_jwt.py --config challenge2_config.json
```
输出:报告输出到 stdout + 证据文件
### 4. 使用自定义端点验证
```
python3 scripts/verify_jwt.py \
--config challenge2_config.json \
--token "eyJhbGciOiJSUzI1NiJ9..."
```
标签:HMAC, Homebrew安装, HS256, JSON Web Token, JWT, Python, RS256, RSA, Streamlit, Web安全, 威胁建模, 安全测试, 密钥混淆攻击, 应用安全, 攻击性安全, 无后门, 算法混淆, 网络安全, 蓝队分析, 访问控制, 身份验证绕过, 逆向工具, 隐私保护