JWT jku / jwk header injection
TL;DR: The token’s header tells the verifier where to find the verification key. Pointing
jkuat an attacker-hosted JWKS, or embedding an attackerjwkdirectly in the header, makes the server validate tokens you signed with your own key.
What it is
JWTs can carry jku (JWK Set URL) or jwk (embedded JSON Web Key) in their header. Libraries that follow either field without an allowlist will fetch or accept attacker-supplied public keys and use them for signature verification. The attacker signs a forged token with their private key, points the header at their public key, and the server happily verifies and trusts the claims.
Preconditions / where it applies
- A service that accepts JWTs and honours
jkuorjwkheaders (olderjsonwebtoken-style libraries, misconfigured Java JOSE stacks) - For
jku: attacker-controlled HTTP origin reachable from the verifier (sometimes the same domain via open redirect or path traversal) - No allowlist of trusted issuer URLs or kid pinning
Technique
jku variant.
-
Generate a keypair and host a JWKS at an attacker URL:
1 2 3
# generate openssl genpkey -algorithm RSA -out priv.pem # publish jwks.json containing the public key, kid, alg=RS256
-
Forge a token whose header references your JWKS:
1
{"alg":"RS256","kid":"atk-1","jku":"https://attacker.example/jwks.json","typ":"JWT"}
- Set claims as desired (
sub,role:admin,expin future), sign withpriv.pem, submit. - If the server restricts
jkuto its own domain, look for: open redirect (jku=https://victim.com/redir?url=attacker), SSRF, or path traversal in a JWKS-serving endpoint that allows escaping to attacker content.
jwk variant. Embed the full public key in the header — no fetch needed:
1
{"alg":"RS256","jwk":{"kty":"RSA","n":"...","e":"AQAB","kid":"atk"}}
Some libraries verify using the embedded key without checking it against a trust store.
kid manipulation is the sibling bug: kid injection can pivot a verifier to a file (../../dev/null) or a SQL row containing attacker data; see also jwt-key-confusion.
Detection and defence
- Disable
jku/jwkheader processing unless explicitly required - If
jkumust be used, pin to an allowlist of exact URLs — no domain wildcards, no following redirects - Pin keys by
kidfrom a server-side keystore; never trust header-provided key material - Log
jku/jwkpresence and alert — most legitimate clients never set them - Use a JWT library audited against
jwt.io’s known-bad list; rejectalg: none
References
- PortSwigger: jku injection — labs and payloads
- PortSwigger: jwk injection — embedded-key variant
- RFC 7515 JOSE — header field definitions