JWT jku and x5u Header Injection in Bug Bounty
A JWT verifier needs a public key. Some tokens tell it where to fetch that key through the jku header, which points to a JSON Web Key Set, or x5u, which points to an X.509 certificate chain. If the server trusts any URL supplied by the token, an attacker can host a key, sign a forged token, and ask the application to verify it against the attacker's own material.
Capture a legitimate token from your account and decode it locally. Record alg, kid, issuer, audience, and existing key-related headers. First confirm normal verification: change one payload byte without resigning and ensure the application rejects it. Otherwise you may be looking at a broader signature-validation failure.
Generate a temporary RSA key pair and expose only its public key through a JWKS endpoint you control. Give the key a unique kid. Build a token that preserves legitimate issuer, audience, timestamps, and claim shape, but set:
{
"alg": "RS256",
"kid": "bugflare-test-19",
"jku": "https://keys.example/jwks.json"
}
Sign it with your private key and use a harmless claim change tied to your own account. If the application fetches your JWKS and accepts the token, the trust model is broken. For x5u, the process is similar, but the URL returns the public certificate or chain expected by the library.
Separate SSRF from signature bypass
An outbound request to your server proves the verifier followed the header, which may itself be blind SSRF. It does not prove authentication bypass. Acceptance of your forged signature is the decisive result. Log the callback and application response so the report can show both.
Test URL restrictions carefully. A verifier may allow only HTTPS but accept any host, check a hostname before following redirects, or trust domains ending in a loose suffix. Use domains you own and avoid redirecting into internal networks. The critical test is whether the key source is pinned to an issuer-controlled allowlist.
kid matching can complicate the proof. Some implementations fetch an approved JWKS yet select a key based on attacker input. Others cache keys by kid without including the issuer in the cache key, creating cross-tenant confusion. Use a fresh identifier and note whether a second request works after your JWKS goes offline; that reveals caching but should not become a cache-flooding experiment.
Keep the claim change modest
Do not forge an executive's identity. Change a non-sensitive claim on your account, or create two owned accounts and sign a token whose subject points from one to the other if the program permits account-level proofs. Stop once the server accepts a token signed by your key. That fact establishes arbitrary-token creation; mass access is unnecessary.
Check issuer and audience validation at the same time. A strong key-source policy can still fail if the backend accepts a valid token intended for another service. Report these as related but distinct validation mistakes.
Root cause and repair
Include the original header, forged header, public JWKS, callback log, and accepted request. Redact private keys and live tokens. State whether the application honored jku, x5u, redirects, and caching.
The verifier should ignore token-supplied key URLs unless a protocol explicitly requires them. Configure a fixed JWKS URI per trusted issuer, require exact issuer and audience values, restrict algorithms, and bind key selection to that issuer. If remote retrieval is unavoidable, use an exact host allowlist, block redirects, and apply outbound network controls. A cryptographic signature only proves something when the verifier already knows whose key is allowed to sign.