eWPTX study materials
eWPTX study resources

The JWT That Was Never Meant to Be Verified

eWPTX · Put your knowledge to work in context.

All case studies

The JWT That Was Never Meant to Be Verified

An internal admin SPA issues JWTs to admin.internal.corp. The token header is {"alg":"RS256","kid":"prod-2024"}. The JWKS endpoint /.well-known/jwks.json is public and returns one RSA key. You have the SPA's JS bundle; it contains a hardcoded fallback public key and a comment // TODO: remove before launch. You are on an internal network segment with a foothold on a low-priv workstation. The client's scope explicitly excludes modifying the JWKS endpoint and excludes DoS. You have captured one valid admin token from a browser cache on the workstation.

1. You want to forge an admin token. Lay out the attack tree you would test, in order of least invasiveness, and what each test tells you.

Read the worked answer
  1. alg:none / algorithm confusion. Re-sign the captured claims with alg:none (drop signature) and with alg:HS256 using the public key bytes as the HMAC secret. Many libraries accept the latter when they don't pin the algorithm. Neither touches the JWKS endpoint, so it's in scope. Success = signature not actually verified.
  2. kid injection. kid is used to select a key; if it's concatenated into a file path or SQL, kid: ../../dev/null (empty key) or a SQLi in kid can forge a valid signature. This is a read-only probe against the verifier, not the JWKS.
  3. Hardcoded fallback key. The bundle comment suggests the fallback public key may be trusted when JWKS lookup fails. If I can induce a lookup failure without DoS (e.g., a malformed kid the resolver rejects), the verifier may fall back to the hardcoded key - and if that key's private half ever leaked (check git history, CI logs, npm cache on the workstation), I can sign directly.
  4. Claim tampering without signature change. If any endpoint trusts sub/role from the token without re-checking, edit claims and replay - signature check may be skipped on some routes.

Order matters: 1 and 2 are single requests and reversible; 3 depends on inducing a failure, so it comes after I've confirmed the verifier's behavior. Each step tells me where trust is broken - algorithm handling, key selection, or fallback logic - which dictates the fix.

2. The `alg:none` and HS256 tests both fail. What does that narrow the bug to, and how do you proceed without touching the JWKS endpoint?

Read the worked answer

Failing both means the verifier does check the signature and does pin the algorithm to RS256 - so the bug isn't in algorithm handling. That leaves the key selection path: kid handling or the fallback logic. I'd focus on kid:

  • Send a token with kid pointing at a path traversal (../../etc/passwd) or a value that resolves to an empty/missing key. If the verifier falls back to the hardcoded public key on lookup failure, I need that key's private half. Search the workstation's git history, CI artifacts, and ~/.npm for the keypair - the // TODO comment hints it shipped.
  • If I find the private key, sign a forged token with kid: prod-2024 (so the verifier picks the real key) but the hardcoded key's signature won't validate against the real key - so instead I target the fallback path: make kid unresolvable so the verifier uses the hardcoded key, and sign with its private half.

If no private key surfaces, I stop forging and pivot to claim trust: replay the captured admin token against admin endpoints with modified role/sub and observe whether authorization re-checks. That's a different bug (broken access control) but often the real impact. Document the kid/fallback weakness as a finding even if unexploited.