Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type JWT Attacks
Difficulty Practitioner
Objective Inject a self-signed JWT using the jwk header parameter to impersonate the administrator and delete carlos

JWT Authentication Bypass via JWK Header Injection

I logged in as wiener:peter and decoded the session JWT:

Screenshot

Header:

{"kid":"fc46afdb-7d08-4f8c-ac63-a5c6b215e68d","alg":"RS256"}

Payload:

{"iss":"portswigger","exp":1783042462,"sub":"wiener"}

alg: RS256 — asymmetric. Without the server's private key, forging a signature normally isn't possible. But the server supports the jwk header parameter, which changes the situation.

RS256 signs with a private key and verifies with the public key. The jwk header parameter was designed to let servers embed the public key directly in the token for federated identity scenarios. The flaw: if the server uses whatever public key is in the jwk field without checking whether that key came from a trusted source, we can supply our own key pair. We sign the modified JWT with our private key, embed our public key in the jwk header, and the server verifies against the key we provided — which passes. The cryptographic verification is sound; the trust chain is broken because the server never checked it owned that key. The jwk header turns key verification into a self-referential loop: the server is asked to verify a token using the key the token itself provides.

I generated a new RSA key pair in Burp's JWT Editor Keys tab:

Screenshot

In Repeater's JSON Web Token tab, I changed sub to administrator:

Screenshot

Then clicked "Attack" → "Embedded JWK" and selected the generated key. Burp's extension handles the three steps that need to stay consistent: it updates the kid in the header to match the embedded key, encodes the public key in JWK format, and re-signs with the private key. Doing this manually is error-prone if any step is out of sync.

Screenshot

The resulting header embedded our public key in the jwk field:

{
    "kid": "eebeae67-b929-46f9-a457-e48a160945c8",
    "alg": "RS256",
    "jwk": {
        "kty": "RSA",
        "e": "AQAB",
        "kid": "eebeae67-b929-46f9-a457-e48a160945c8",
        "n": "s6pgPehsPomnTsjEP19FjzdV..."
    }
}
Screenshot

Unlike the HS256 brute-force lab, there was no secret to recover — we generated our own key pair from scratch and the server accepted it. The attack works even against a server with a perfectly strong RS256 private key. The fix is a strict key whitelist: the server should only accept jwk values matching trusted public keys, or reject the parameter entirely and always look up keys internally.

I pasted the forged token into the browser's session cookie storage:

Screenshot

Admin panel loaded. Deleted carlos:

Screenshot

Lab solved

Resources