JSON Web Token Exploitation
Back to VaultJWT attacks can compromise authentication systems. This lab teaches recognition and defense of JWT vulnerabilities. Only test on systems you own or have explicit permission to assess.
JSON Web Tokens consist of three Base64URL-encoded parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiam9obiIsInJvbGUiOiJ1c2VyIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
└────────── Header ──────────┘.└─────────────── Payload ───────────────────────────────────┘.└────────── Signature ──────────┘
Contains the token type and signing algorithm:
{
"alg": "HS256", // Signing algorithm
"typ": "JWT" // Token type
}
Contains the actual data - user info, permissions, expiration:
{
"sub": "1234567890", // Subject (user ID)
"name": "John Doe", // Custom claim
"role": "user", // Authorization level
"iat": 1516239022, // Issued at timestamp
"exp": 1516242622 // Expiration timestamp
}
Ensures token integrity - created using the header, payload, and secret:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Paste a JWT token to decode it, or modify the payload to simulate attacks:
Some libraries accept tokens with "alg": "none", allowing unsigned tokens:
// Original header
{"alg": "HS256", "typ": "JWT"}
// Malicious header
{"alg": "none", "typ": "JWT"}
// The token now has NO signature requirement!
// Just modify the payload and leave signature empty
If server expects RS256 (asymmetric) but accepts HS256 (symmetric):
// Server's public key (meant for verification only)
// Attacker uses it as HMAC secret
// Original: RS256 with private key signature
// Attack: HS256 signed with PUBLIC key as secret
// If server uses public key for both algorithms,
// attacker can forge valid signatures!
If the secret is weak (like "secret", "password", etc.):
# Using jwt-cracker or hashcat
hashcat -a 0 -m 16500 jwt.txt wordlist.txt
# Common weak secrets:
# secret, password, 123456, jwt_secret, key
Modifying claims without proper signature verification:
// Original payload
{"user": "john", "role": "user", "id": 1001}
// Modified payload (privilege escalation)
{"user": "john", "role": "admin", "id": 1}
Using stolen tokens before expiration, or if no expiration is set:
// Token without expiry - valid forever!
{"user": "john", "role": "user"}
// Proper token with expiry
{"user": "john", "role": "user", "exp": 1703566400}
Modify claims and generate a new (unsigned) token to understand the attack:
// Python example with PyJWT
import jwt
try:
# ALWAYS specify the expected algorithm!
decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidSignatureError:
return "Invalid signature"
except jwt.ExpiredSignatureError:
return "Token expired"
// Explicitly whitelist allowed algorithms
const options = {
algorithms: ['HS256', 'RS256'], // Only these!
// Never include 'none'
};
jwt.verify(token, secret, options);
# Generate a cryptographically secure secret
import secrets
SECRET_KEY = secrets.token_hex(32) # 256 bits
# Or use RS256 with proper key pair
{
"sub": "user123",
"iat": 1703480000, // Issued at
"exp": 1703483600, // Expires in 1 hour
"nbf": 1703480000 // Not valid before
}
# Maintain a blacklist of revoked tokens (by jti claim)
revoked_tokens = redis.get("revoked_tokens")
if token.jti in revoked_tokens:
return "Token has been revoked"