| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | JWT Attacks |
| Difficulty | Expert |
| Objective | Obtain the server's public RSA key, use it as an HMAC secret to sign a forged HS256 token, and access /admin to delete carlos |
JWT Authentication Bypass via Algorithm Confusion¶
I logged in as wiener:peter and decoded the session JWT:
Header: alg: RS256. Checking /jwks.json:
The server's RSA public key is available at the standard endpoint — it's meant to be public. In normal RS256 operation, knowing the public key gives you nothing, since only the private key can produce valid signatures. The algorithm confusion attack is the one scenario where the public key is also the attack payload.
RS256 is asymmetric: sign with private key, verify with public key. HS256 is symmetric: same secret for both. Some JWT libraries, when they receive a token claiming alg: HS256, use the server's configured RSA public key as the HMAC secret for verification — because the code paths share a generic "key material" variable without enforcing which algorithm that key belongs to. The attack exploits that gap: we obtain the server's public key, forge an HS256 token signed with it, and the server verifies it using that same public key as the HMAC secret. The cryptographic verification succeeds because we computed exactly the same thing.
I imported the JWK into Burp's JWT Editor as a new RSA key:
I right-clicked and selected "Copy Public Key as PEM", then base64-encoded the PEM block in Burp's Decoder:
The encoding chain matters: the JWK gives key material in base64url-encoded modulus and exponent form; we reconstruct it as PEM, then base64-encode the entire PEM block to use as the k value in a symmetric JWK. Using the JWK components directly or skipping the PEM step produces a different byte sequence and the signature won't match.
I created a symmetric key in JWT Editor with the base64-encoded PEM as k:
I modified the JWT. The kid stays the same — it still maps to the server's key — but alg changes to HS256, and sub becomes administrator:
Header:
{
"kid": "6a2f86f3-e6a7-4a48-b11b-e50c654fab3a",
"alg": "HS256"
}
Signed with the symmetric key containing the server's public key. The token is now signed with HMAC-SHA256(header.payload, server_public_key) — exactly what the vulnerable server will compute when it verifies. Pasted into the browser:
Admin panel loaded. The fix for this is algorithm whitelisting: the server should reject any token whose alg doesn't match the algorithm it configured for that kid. The token itself shouldn't dictate the verification algorithm. Deleted carlos:
Lab solved