JWT Key Confusion (Static Artifact): Public Key as HMAC Secret for Token Signature Forgery
Theory
Why This Matters
The JWT algorithm confusion attack was systematically documented by PortSwigger Research in 2022 and has since appeared in numerous real-world bug bounty reports and CVEs. The attack exploits a fundamental confusion between asymmetric and symmetric cryptographic operations in JWT verification code. Unlike the alg:none attack, this vulnerability is more subtle: the server is performing cryptographic verification — it is just verifying with the wrong key material because it trusts the algorithm specified in the attacker-controlled token header. Applications that publish their RSA public key (common in OAuth 2.0 / OIDC deployments via the JWKS endpoint) inadvertently expose all the material an attacker needs to exploit this vulnerability.
Core Concept
RS256 is an asymmetric JWT signing algorithm: the server signs tokens with its RSA private key and verifies them with the corresponding RSA public key. The public key is, by definition, public — it is often served at /.well-known/jwks.json or /api/auth/keys to allow third-party services to verify tokens without sharing secret key material. This is the fundamental advantage of asymmetric schemes.
The RS256→HS256 key confusion attack exploits a vulnerability in JWT libraries that allow the client-specified algorithm to influence verification behaviour without validating that the algorithm is consistent with what the server expects. The attack proceeds as follows: the attacker obtains the server's RSA public key (from the JWKS endpoint, from a certificate, or from an existing token's public key fields), then crafts a new JWT with the algorithm changed from RS256 to HS256 in the header, and signs it using HMAC-SHA256 with the RSA public key as the HMAC secret. When the vulnerable server receives this token, it reads alg: HS256 from the header, retrieves what it believes is the HMAC secret (its stored public key), and runs HMAC-SHA256 verification — which succeeds because the attacker used the same public key to sign.
The invariant violated is that the server should enforce a single expected algorithm for token verification regardless of what the token header claims. The vulnerable code pattern is: verify(token, key, algorithm=token.header.alg) rather than verify(token, key, algorithm=server_expected_algorithm).
Extracting the public key from a JWKS endpoint gives the RSA modulus and exponent in JSON format (n, e fields). These must be converted to PEM format for use with jwt_tool. If the JWKS endpoint is not available, the public key can sometimes be recovered from two JWT signatures using RSA parameter recovery techniques.
Technical Deep-Dive
Extracting the RSA public key from a JWKS endpoint:
# Fetch the JWKS
curl https://target.example.com/.well-known/jwks.json
# Example response:
# {"keys":[{"kty":"RSA","use":"sig","n":"sIf4bhX...","e":"AQAB","kid":"1"}]}
# Convert JWKS to PEM using jwt_tool helper
python3 -c "
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.primitives import serialization
import base64, struct
def b64url_to_int(s):
s += '=='
return int.from_bytes(base64.urlsafe_b64decode(s), 'big')
n = b64url_to_int('sIf4bhX...') # replace with actual n value
e = b64url_to_int('AQAB')
pub = RSAPublicNumbers(e, n).public_key()
pem = pub.public_bytes(
serialization.Encoding.PEM,
serialization.PublicFormat.SubjectPublicKeyInfo
)
print(pem.decode())
" > public.pem
Performing the RS256→HS256 confusion attack with jwt_tool:
# -X k = key confusion attack
# -pk = path to the RSA public key in PEM format
jwt_tool "$JWT" -X k -pk public.pem
# jwt_tool will:
# 1. Change alg from RS256 to HS256 in the header
# 2. Modify the payload (you will be prompted or use -I -pc claim value)
# 3. Sign with HMAC-SHA256 using the PEM-encoded public key as the secret
# 4. Output the forged JWT
Manual implementation for verification:
import jwt # PyJWT
from cryptography.hazmat.primitives import serialization
with open('public.pem', 'rb') as f:
pub_key_bytes = f.read()
# Strip PEM headers to get raw key bytes for HMAC
# (jwt_tool handles this; manual implementation must match the exact byte format)
payload = {"user": "alice", "role": "admin"}
forged = jwt.encode(payload, pub_key_bytes, algorithm="HS256")
print(forged)
Security Assessment Methodology
- Identify the JWT algorithm — Decode the header of any JWT in use. If
algisRS256,RS384,RS512,ES256, orPS256(any asymmetric algorithm), this attack is potentially applicable. - Obtain the RSA public key — Check
/.well-known/jwks.json,/api/keys,/oauth/discovery, and/api/auth/certs. Also check the application's source code repository, documentation, or TLS certificate. Save the key aspublic.pem. - Run jwt_tool -X k — Execute
jwt_tool <token> -X k -pk public.pem. Review the output: jwt_tool will indicate whether the attack produced a different HTTP response from the server. - Modify the payload for privilege escalation — Use
jwt_tool <token> -X k -pk public.pem -I -pc role -pv adminto simultaneously perform the confusion attack and modify a specific claim. - Submit the forged token — Use Burp Repeater to submit the forged token in the Authorization header. Confirm that the server returns a response consistent with the claimed privileges.
- Test edge cases — Try RS384 and RS512 as the original algorithm. Try ES256 (ECDSA) if present; the same confusion attack applies to elliptic curve algorithms using the public key as HMAC secret.
Defensive Countermeasure — Configure the JWT verification function with a hard-coded expected algorithm list on the server, independent of the token header:
jwt.decode(token, public_key, algorithms=["RS256"])— thealgorithmsparameter must be a server-controlled constant, never derived from the token being verified.
Common Assessment Errors
- Not checking for the JWKS endpoint — Many testers test JWT attacks manually but skip the public key retrieval step. Check all common JWKS paths before concluding the public key is unavailable.
- Using the wrong key format — HMAC-SHA256 signs bytes. The public key must be provided in the exact byte format that the vulnerable library uses internally (typically PEM with headers). Using just the modulus bytes or a Base64-decoded version produces different HMAC outputs. Use jwt_tool to avoid this complexity.
- Testing only RS256 — The confusion attack applies to any asymmetric algorithm (RS384, RS512, ES256, PS256). Test all algorithms observed in token headers.
- Not verifying the forged token grants elevated access — A forged token that is accepted with 200 OK but returns data for the correct (non-elevated) user does not confirm privilege escalation. Inspect the response body for evidence of the claimed role.
- Skipping the alg:none pre-check — Always try alg:none first; it is simpler and, if successful, immediately confirms the severity. Key confusion is the fallback when none is rejected.
- Forgetting to test kid-based key selection — If the token includes a
kidheader, the server may select the verification key based on it. Test kid injection (Card 12) in parallel.
NICE Framework Alignment
| Code | Knowledge/Skill/Task Statement | How This Card Develops It |
|---|---|---|
| K0007 | Knowledge of authentication, authorisation, and access control methods | Explains the asymmetric JWT security model and the key confusion flaw that undermines it |
| K0065 | Knowledge of policy-based and role-based access controls | Demonstrates how algorithm confusion enables arbitrary role claim insertion into verified tokens |
| S0001 | Skill in conducting vulnerability scans and recognising vulnerabilities in security systems | Develops JWKS key extraction and jwt_tool key confusion attack skills |
| T0028 | Conduct and/or support authorised penetration testing on enterprise network assets | Provides a complete RS256→HS256 assessment methodology applicable to OAuth and OIDC deployments |
Further Reading
- JWT Algorithm Confusion Attacks — PortSwigger Web Security Research (2022)
- RFC 7517: JSON Web Key (JWK) — IETF (Jones)
- JWT Security Best Practices — IETF RFC 8725
Challenge Lab
Reinforce your learning with a hands-on generated challenge based on this card's competency.