JWT Algorithm Confusion: Turning RS256 Tokens into HS256 Disasters 🔄

JWT Algorithm Confusion: Turning RS256 Tokens into HS256 Disasters 🔄
Understanding the Critical Security Flaw That Turns Public Keys into Secrets
In the landscape of modern web application security, JSON Web Tokens (JWTs) have become ubiquitous for handling authentication and authorization. However, a subtle yet devastating vulnerability lurks within many JWT implementations: algorithm confusion attacks. This attack vector exploits the way servers validate JWT signatures, allowing attackers to forge tokens by manipulating the algorithm specified in the token header.
What is JWT Algorithm Confusion?
Algorithm confusion vulnerabilities typically arise due to flawed implementation of JWT libraries, where many libraries provide a single, algorithm-agnostic method for verifying signatures. The vulnerability occurs when a server fails to properly enforce which cryptographic algorithm should be used to verify a JWT’s signature.
Algorithm confusion occurs when a system fails to properly verify the type of signature used in a JWT, allowing an attacker to exploit insufficient distinction between different signing methods. The most dangerous variant involves switching from RS256 (RSA with SHA-256, an asymmetric algorithm) to HS256 (HMAC with SHA-256, a symmetric algorithm).
The Fundamental Difference Between RS256 and HS256
Understanding algorithm confusion requires grasping the fundamental difference between symmetric and asymmetric cryptography:
RS256 (RSA + SHA-256) - Asymmetric: - Uses a private key to sign tokens - Uses a mathematically related public key to verify signatures - The private key must be kept secret - The public key can be shared openly - Two different keys for two different purposes
HS256 (HMAC + SHA-256) - Symmetric: - Uses a single secret key for both signing and verification - The same key performs both operations - The secret must be kept confidential - Anyone with the secret can create and verify tokens
How the Attack Works: The Deadly Switch
The most common variant involves swapping an RS256 (RSA) token to HS256 (HMAC), and then using the RSA public key as the HMAC secret. Here’s the attack flow:
Step 1: Obtain a Valid JWT
The attacker first obtains a legitimate JWT from the application, typically signed with RS256:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwicm9sZSI6InVzZXIifQ.signature
This token has three parts:
- Header: {"alg":"RS256","typ":"JWT"}
- Payload: {"sub":"user123","role":"user"}
- Signature: Verified using the server’s public RSA key
Step 2: Acquire the Public Key
Servers sometimes expose their public keys as JSON Web Key (JWK) objects via a standard endpoint mapped to /jwks.json or /.well-known/jwks.json. Attackers can obtain public keys through several methods:
- Standard JWKS endpoints (
/.well-known/jwks.json) - Server documentation or configuration files
- SSL/TLS certificates
- Deriving keys from multiple captured JWTs using tools like jwt_forgery.py
Step 3: Modify the Token Header and Payload
The attacker changes the algorithm in the header from RS256 to HS256 and modifies the payload to escalate privileges:
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "user123",
"role": "admin"
}
Step 4: Sign with the Public Key as HMAC Secret
Forging tokens involves signing the token payload using the PEM-formatted public key as an HMAC key. The attacker signs the modified token using HS256, treating the server’s public RSA key as the HMAC secret.
Step 5: Server Accepts the Forged Token
When the server receives this malicious token, the vulnerability manifests in how the verification method processes it:
Problems arise when website developers assume the verify method will exclusively handle JWTs signed using an asymmetric algorithm like RS256, and always pass a fixed public key to the method.
// Vulnerable code pattern
publicKey = <public-key-of-server>;
token = request.getCookie("session");
verify(token, publicKey);
If the server receives a token signed using a symmetric algorithm like HS256, the library’s generic verify method will treat the public key as an HMAC secret. The server unwittingly uses its own public key as the HMAC secret to verify the attacker’s signature, which validates successfully because the attacker used the same public key to create the signature.
Real-World Impact: Recent Vulnerabilities
Algorithm confusion is not merely a theoretical threat. Recent discoveries demonstrate its continued prevalence:
CVE-2024-54150 (December 2024)
A vulnerability was discovered in the cjwt library where the function cjwt_decode does not require developers to pick an algorithm, and the code for handling HMAC and RSA keys was identical. The library’s switch statement would process HS256 tokens using the provided key without detecting that a public key was passed as a secret.
CVE-2024-37568 (2024)
This critical vulnerability in Authlib, a popular Python library for OAuth and OpenID Connect implementations, arose when the ‘alg’ claim was missing or incorrect, causing Authlib to incorrectly default to HMAC verification even when a public key was present.
CVE-2023-48238 (2023)
The json-web-token library was found vulnerable because the algorithm to use for verifying the signature was taken from the JWT token itself, which at that point was still unverified and shouldn’t be trusted.
Why This Vulnerability Exists: The Root Causes
Flawed Library Design
The JWT header contains an alg parameter that tells the server which algorithm was used to sign the token and which algorithm it needs to use when verifying the signature, creating an inherently flawed design because the server has no option but to implicitly trust user-controllable input from the token which hasn’t been verified.
Developer Misunderstanding
Many developers misunderstand the security implications of generic verification methods. They assume that passing a public key automatically means the library will only accept asymmetric signatures, not realizing that the library trusts the algorithm specified in the unverified token header.
Insufficient Validation
Applications often fail to validate that the algorithm in the token header matches their expected algorithm. Without this check, attackers can substitute algorithms at will.
Demonstrating the Attack: Practical Exploitation
Here’s how an attacker might execute this attack using common tools:
Using jwt_tool
# Extract the public key from JWKS endpoint
curl https://target.com/.well-known/jwks.json > jwks.json
# Create a forged token
python jwt_tool.py original_token.txt -X k -pk public_key.pem
Manual Python Implementation
import jwt
import base64
# Read the public key
with open('public_key.pem', 'r') as f:
public_key = f.read()
# Create malicious payload
payload = {
'sub': 'user123',
'role': 'admin',
'iat': 1234567890
}
# Sign using HS256 with public key as secret
forged_token = jwt.encode(
payload,
public_key,
algorithm='HS256'
)
print(forged_token)
Using Burp Suite
Once you have the public key in a suitable format, you can modify the JWT however you like, ensuring that the alg header is set to HS256, then sign the token using the HS256 algorithm with the RSA public key as the secret.
Advanced Exploitation Techniques
Public Key Recovery
In cases where the public key isn’t readily available, you may still be able to test for algorithm confusion by deriving the key from a pair of existing JWTs using tools such as jwt_forgery.py.
The mathematical relationship between multiple signatures created with the same private key can be exploited to reconstruct the public key, requiring only two JWTs signed with the same key.
Format Sensitivity
The public key you use to sign the token must be absolutely identical to the public key stored on the server, including using the same format and preserving any non-printing characters like newlines. Attackers often need to experiment with different key formats:
- PEM format with headers
- Raw key material
- Different line ending styles (CRLF vs LF)
- With or without trailing newlines
Detection and Testing
Identifying Vulnerable Applications
Security researchers can test for algorithm confusion by:
- Analyzing JWT headers to identify the signing algorithm in use
- Locating public keys through JWKS endpoints or other means
- Attempting the attack by modifying the algorithm and signing with the public key
- Observing server behavior to see if the forged token is accepted
Automated Scanning
From Burp Suite Professional 2022.5.1, Burp Scanner can automatically detect a number of vulnerabilities in JWT mechanisms, including algorithm confusion attacks.
Defense Strategies: Protecting Your Applications
1. Explicitly Specify Expected Algorithms
JWT libraries should add an algorithm parameter to their verification function, and the server should already know what algorithm it uses to sign tokens.
Secure Implementation:
// Node.js with jsonwebtoken
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
# Python with PyJWT
jwt.decode(token, public_key, algorithms=['RS256'])
// Java with jjwt
Jwts.parserBuilder()
.setSigningKey(publicKey)
.requireAlgorithm(SignatureAlgorithm.RS256)
.build()
.parseClaimsJws(token);
2. Use Algorithm Whitelisting
It is preferable to adopt a whitelist, explicitly defining the authorised algorithms such as HS256 or RS256, which guarantees strict control and avoids loopholes caused by lax interpretations of the algorithm.
3. Separate Verification Logic by Key Type
Never use the same verification method for both symmetric and asymmetric keys. Implement separate code paths:
function verifyToken(token, expectedAlgorithm, key) {
// Extract algorithm from header for logging only
const header = JSON.parse(
Buffer.from(token.split('.')[0], 'base64').toString()
);
// NEVER trust the header algorithm
// Always use the expected algorithm
if (expectedAlgorithm === 'RS256') {
return verifyRS256(token, key);
} else if (expectedAlgorithm === 'HS256') {
return verifyHS256(token, key);
}
throw new Error('Unsupported algorithm');
}
4. Implement Type Checking
Some libraries now include protections that detect when a public key is being used where a symmetric secret is expected:
function isPublicKey(key) {
return key.includes('BEGIN PUBLIC KEY') ||
key.includes('BEGIN RSA PUBLIC KEY');
}
function verifyHS256(token, secret) {
if (isPublicKey(secret)) {
throw new Error('Public key cannot be used as HMAC secret');
}
return jwt.verify(token, secret, { algorithms: ['HS256'] });
}
5. Reject the “none” Algorithm
The none algorithm is intended to be used for situations where the integrity of the token has already been verified, but unfortunately some libraries treated tokens signed with the none algorithm as a valid token with a verified signature.
Always explicitly reject tokens using the “none” algorithm in production:
const decoded = jwt.verify(token, key, {
algorithms: ['RS256', 'HS256'],
// The 'none' algorithm is automatically rejected
});
6. Use Strong Secrets for HMAC
When implementing JWT applications, developers sometimes make mistakes like forgetting to change default or placeholder secrets, making it trivial for an attacker to brute-force a server’s secret using a wordlist of well-known secrets.
For HS256 implementations: - Use cryptographically random secrets of at least 256 bits - Store secrets securely in environment variables or secret management systems - Rotate secrets regularly - Never hardcode secrets in source code
7. Validate All Claims Thoroughly
Beyond algorithm validation, implement comprehensive claim validation:
jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://trusted-issuer.com',
audience: 'your-application',
clockTolerance: 60 // seconds of leeway for time comparisons
});
8. Keep Libraries Updated
To address CVE-2024-37568, it is imperative to upgrade Authlib to version 1.3.1 or later, which incorporates robust fixes to prevent algorithm confusion and ensure correct JWT validation.
Regularly update JWT libraries to benefit from security patches and improved validation logic.
Best Practices for JWT Security
Comprehensive Security Checklist
Algorithm Management: - ✅ Explicitly specify allowed algorithms in verification calls - ✅ Never trust the algorithm from the token header - ✅ Disable the “none” algorithm in production - ✅ Use asymmetric algorithms (RS256, ES256) for most use cases - ✅ Implement algorithm whitelisting at the application level
Key Management: - ✅ Store private keys securely using key management services - ✅ Use strong, randomly generated secrets for HMAC - ✅ Rotate keys regularly with grace periods for key rollover - ✅ Keep public keys accessible but protect private keys - ✅ Never embed keys in source code or configuration files
Token Handling: - ✅ Set short expiration times (15 minutes or less for access tokens) - ✅ Implement refresh token rotation - ✅ Validate all standard claims (iss, aud, exp, nbf, iat) - ✅ Use HTTPS exclusively for token transmission - ✅ Store tokens securely (HttpOnly cookies for web apps)
Implementation: - ✅ Use well-maintained, up-to-date JWT libraries - ✅ Implement proper error handling without leaking information - ✅ Log security events for monitoring and incident response - ✅ Perform regular security audits and penetration testing - ✅ Educate developers on JWT security principles
Selecting the Right Algorithm
For New Applications: - RS256 or ES256: Best for most scenarios requiring public key distribution - PS256: Enhanced security over RS256 with probabilistic signatures - EdDSA: Most secure and efficient, excellent for new implementations
Avoid: - HS256 for public APIs where the secret might be exposed - Weak keys: Minimum 2048 bits for RSA, 256 bits for ECDSA - Algorithm flexibility: Don’t support multiple algorithms unless absolutely necessary
Testing Your Implementation
Vulnerability Assessment Steps
- Review verification code to ensure algorithms are explicitly specified
- Test with modified tokens where the algorithm is changed
- Attempt public key substitution to verify protections are in place
- Check for “none” algorithm acceptance in all environments
- Verify key format sensitivity doesn’t affect security
Security Testing Tools
- jwt_tool: Comprehensive JWT testing toolkit
- Burp Suite: Web application security testing with JWT extensions
- OWASP ZAP: Open-source security scanner with JWT support
- Custom scripts: Python or Node.js scripts for specific test cases
The Broader Context: JWT Security Landscape
Algorithm confusion is just one of several JWT vulnerabilities that applications must defend against:
- Weak secrets: Brute-forcing HMAC secrets
- Missing signature verification: Accepting unsigned tokens
- Token leakage: Exposure through logs, URLs, or client-side storage
- Replay attacks: Reusing captured tokens
- JKU/X5U injection: Header parameter manipulation
- Kid injection: Path traversal through key identifier
JWTs are not secure just because they are JWTs; it’s the way in which they’re used that determines whether they are secure or not.
Conclusion: Vigilance is Essential
JWT algorithm confusion represents a critical security vulnerability that has affected major libraries and continues to surface in new implementations. The attack exploits a fundamental trust issue: allowing unverified tokens to dictate how they should be verified.
Never trust the “alg” field from the JWT itself, and enforce the expected algorithm at the configuration level. By implementing explicit algorithm validation, using algorithm-specific verification methods, and following security best practices, developers can protect their applications from this devastating attack vector.
The key takeaway: Never trust user input, especially when it determines security-critical operations. The algorithm field in a JWT header is user-controlled data from an unverified source. Treat it with the same skepticism you would treat any other untrusted input, and always enforce your security policy explicitly in code.
As authentication mechanisms continue to evolve, staying informed about vulnerabilities like algorithm confusion and implementing defense-in-depth strategies remains essential for maintaining application security. Regular security audits, developer education, and proactive monitoring are the cornerstones of a robust JWT security posture.
Resources for Further Learning:
- OWASP JWT Security Cheat Sheet
- RFC 7519: JSON Web Token (JWT)
- RFC 8725: JWT Best Current Practices
- PortSwigger Web Security Academy: JWT Attacks
- Burp Suite JWT Testing Tools
Stay secure, verify explicitly, and never trust the algorithm header! 🔒