skip to content

Why must a JWT verifier never take its key from the token's own kid or jku header?

level: seniorimportance: should knowfreq 42%

answer

  1. The token is telling you how to check it
  2. One field names a place, one names an entry
  3. Traversal and injection both apply to lookups
  4. Allowlists on URLs are fragile
  5. Keys come from configuration, always

basics

~20 s

Those header fields are attacker-controlled. A jku or x5u URL can point at a key set the attacker hosts, and a kid used as a file path or SQL fragment can select a key they control or inject into the query. Either way they sign their own forged token.

solid answer

~50 s

JOSE header parameters `kid`, `jku` and `x5u` describe where the verifying key can be found — and they travel inside the very token you are trying to authenticate, so they are attacker input. If a verifier fetches the key set from the token's `jku`, an attacker generates their own key pair, hosts the public key, points `jku` at it, signs a forged token with their private key, and verification succeeds. `kid` fails differently: implementations that use it as a filesystem path allow traversal to a predictable file whose contents the attacker knows (an empty or fixed-content file makes the MAC key predictable), and implementations that interpolate it into SQL allow injection that returns an attacker-chosen key. The rule is one line: key material comes from verifier configuration. Treat `kid` as an opaque lookup key into keys you already trust, ignore `jku` and `x5u`, and reject anything that does not match.

code

json · 5 lines
json
{
  "alg": "RS256",
  "kid": "attacker-key-1",
  "jku": "https://attacker.example/keys.json"
}

go deeper

for a junior

Know that JOSE header fields such as kid and jku come from the token itself and are therefore untrusted, and that verification keys must come from your own configuration.

for a middle

Explain the two distinct mechanisms: jku and x5u redirect where the key is fetched from, while kid poisons a lookup when it is used to build a path or a query.

for a senior

Show the hardening end to end — disable token-supplied key sources, exact-match allowlists only if unavoidable, bounded kid patterns resolved against an in-memory key map, plus alerting on unexpected header parameters.

for a principal

Own the invariant and the blast radius: no service may take key material from a credential, and note that a jku fetch is also an unauthenticated outbound-request primitive that egress policy must account for.

## What these header parameters are for JSON Web Signature defines several header parameters that help a recipient locate the key: `kid` (key identifier), `jku` (a URL for a JWK Set), `x5u` (a URL for an X.509 certificate chain), plus `x5c` (an inline certificate chain) and `x5t` (a certificate thumbprint). They exist for protocols where both parties already agree, out of band, on which sources are legitimate. The pitfall is treating them as *authoritative* rather than as hints, in a context where the token arrives from an untrusted caller. A credential that tells you how to check it is a credential that checks itself. ## The jku and x5u attack Straightforward and complete: 1. The attacker generates their own RSA key pair. 2. They publish the public half as a JWK Set at a URL they control. 3. They craft a token with the claims they want, set `jku` to that URL, and set `kid` to match their published key. 4. They sign with their private key. 5. A verifier that fetches the key set from the token's `jku` retrieves the attacker's public key, verifies the attacker's signature against it, and succeeds. Every cryptographic step is correct. The failure is entirely one of **trust anchoring** — the verifier authenticated the token against a key with no relationship to the issuer. A partial mitigation is an allowlist of hosts or exact URLs for `jku`. It works only if it is an exact-match allowlist, and it is fragile: prefix or substring matching on URLs is defeated by lookalike hosts, userinfo tricks, redirects and open-redirect endpoints on the allowed host. Also treat it as a server-side request forgery surface — you have created a feature where an unauthenticated caller makes your service issue an outbound HTTP request to a URL of their choosing. The safe default is not to support these parameters at all: configure the key set location per issuer and ignore what the token says. `x5c` deserves its own note. An embedded certificate chain that verifies internally proves nothing on its own — anyone can build a self-signed chain. It is only meaningful if you validate it up to a trust anchor you configured and check the expected subject. ## The kid injection attacks `kid` carries no URL, so it looks harmless; the danger comes from what implementations *do* with the value. It is a string chosen by the caller, and it has repeatedly been used as an index into something that interprets strings: - **Filesystem path.** A verifier that reads a key from a directory joined with `kid` allows path traversal — `../../` sequences reaching a file the attacker can predict. Pointing at a file with known or empty contents makes the symmetric key predictable, so the attacker signs a token whose MAC the verifier will reproduce exactly. - **SQL query.** A verifier that interpolates `kid` into a query is vulnerable to injection, and a crafted value can make the query return a constant the attacker chose — again yielding a known key. - **Command or template evaluation.** Any path where the value reaches a shell or an expression evaluator turns key lookup into code execution. The unifying pattern: `kid` is untrusted input reaching an interpreter. The defence is the same as anywhere else — never concatenate it into a path, a query or a command. Look it up as an **exact key in a map** of already-loaded trusted keys, or as a bound parameter in a parameterised query against a table that only ever contains your own keys. Validate its shape (a short, bounded, character-restricted string) and reject anything unexpected before it is used. ## The rule that covers all of these A verifier's key material must come from configuration or from a key set fetched from a configured location. The token may say *which* of your trusted keys to use; it may never say *where to get* a key or *what* the key is. Stated once, that rule closes `jku`, `x5u`, `x5c`, and every `kid` interpolation bug at the same time, and it is the same rule that closes the unsecured-token and algorithm-substitution attacks: **the credential must not influence how the credential is checked.** ## Practical hardening - Disable `jku`, `x5u` and `x5c` handling unless a specific protocol requires them; if required, use an exact-match allowlist plus outbound network egress controls. - Require `kid` to match a bounded pattern, and resolve it only against an in-memory map of trusted keys. - Never let key lookup touch the filesystem, a shell, or a string-built query. - Log and alert on tokens whose `kid` is unknown or whose header carries unexpected parameters; legitimate clients do not produce these, so a non-zero rate is either a misconfiguration or an active probe. - Include this in the negative test suite: a token signed with an attacker key plus a `jku` pointing at a local test server must be rejected. ## Answering it well Separate the two mechanisms clearly — `jku`/`x5u` redirect the *source* of the key, `kid` poisons the *lookup* — then state the single rule that fixes both, and mention the outbound-request angle for `jku`, which shows you see it as a server-side request forgery surface and not just an authentication bug.

  • Is an allowlist of permitted jku hosts an adequate defence?
    Only as a fallback, and only with exact matching. Prefix or substring checks on URLs fall to lookalike hosts, userinfo segments, and open redirects on the permitted host, and you still expose an outbound request that an unauthenticated caller controls — a server-side request forgery surface. Configuring the key set location per issuer and ignoring `jku` entirely is strictly safer.
  • How can a kid used as a filesystem path lead to a forged token?
    Path traversal lets the attacker point the lookup at a file whose contents they can predict — an empty file, or a fixed static file. The verifier then loads that predictable content as the key, so the attacker computes the matching MAC themselves and signs a token the verifier will accept. The fix is to resolve `kid` against an in-memory map of trusted keys, never against the filesystem.
  • Does an x5c certificate chain inside the header let a verifier trust the token?
    Not by itself. Anyone can build a self-signed chain that validates internally, so an embedded chain proves nothing unless you verify it up to a trust anchor you configured and check that the subject is the expected issuer. Without those steps it is the same attacker-supplied-key problem in certificate form.
  • What signal in production suggests someone is probing these header parameters?
    Rejections for unknown `kid` values, and tokens arriving with `jku`, `x5u` or `x5c` present at all when your issuer never sets them. Legitimate clients produce a small, stable set of key identifiers and no key-source parameters, so any sustained non-zero rate is either a misconfigured service or an active attempt worth alerting on.

saying these in an interview costs you the question

  • Fetches the key set from the URL inside the token
  • Concatenates kid into a filesystem path or SQL string
  • Trusts an embedded certificate chain because it validates internally
  • Allowlists jku hosts with a substring or prefix check
  • Treats kid as harmless because it is not a URL

context