HTB CWEE study materials
HTB CWEE study resources

JWT kid Path Traversal and GraphQL Schema Discovery

HTB CWEE · Put your knowledge to work in context.

All case studies

JWT kid Path Traversal and GraphQL Schema Discovery

A public GraphQL API at https://api.example.com/graphql uses JWT bearer tokens. The middleware reads the kid header, builds path.join('/app/keys', kid), reads that file, and verifies the token with HS256 using the file contents as the secret. Introspection is disabled, but field suggestions are enabled. The me query returns the current user. The user(id: ID!) query is meant for admins and authorizes by checking role === 'admin' in the verified JWT payload. You have a normal user token for alice. The container has /dev/null and /etc/hostname. The schema also contains an admin-only users query. You need bob's email and API key; bob's UUIDv4 is unknown. The API returns descriptive GraphQL validation errors.

1. Can you forge an admin token? Give the exact `kid`, algorithm, and secret, and explain why it works.

Read the worked answer

Yes, if the verifier uses the file contents as the HMAC key and the path join is not constrained. Set kid to ../../../../dev/null. path.join('/app/keys', '../../../../dev/null') resolves to /dev/null; reading it returns an empty buffer or string. Sign a payload {"sub":"alice","role":"admin"} with HS256 and the empty secret. In Node, jwt.sign(payload, '') may reject an empty string, so use a raw HMAC or a library that accepts a zero-length key. The server verifies with the same empty key, so the forged token passes.

Send it as Authorization: Bearer <token>. This is not algorithm confusion; it is key-selection path traversal. If .. is normalized away, try URL-encoded %2e%2e%2f, but path.join normally collapses traversal before the read. A leading slash is not enough because path.join('/app/keys','/dev/null') yields /app/keys/dev/null; the traversal is required. Confirm with { me { role } } before using admin fields.

2. Introspection is disabled. How do you discover the admin-only `users` field and retrieve bob's data?

Read the worked answer

Use field suggestions. Send { usr }; the validation error will name the closest field, for example Cannot query field "usr" on type "Query". Did you mean "user"?. Try { user } and { users }; a suggestion for users confirms the field. With the forged admin token, query { users { id email apiKey } }. If users requires an argument, the error will name it; try { users(first: 100) { id email apiKey } }. If the field is hidden from suggestions, use the forged token with a short wordlist of common field names, but only after confirming the token works with { me { role } }.

Retrieve bob by filtering the returned list client-side. If users is not present, use user(id:), but you still need the UUID; the admin token may unlock userByEmail(email:), which suggestions will reveal. Capture the full GraphQL response and the forged token's kid as evidence. Remediation: never derive the verification key from user input; use a fixed key store keyed by an allowlisted identifier, reject path separators, prefer asymmetric algorithms, and enforce authorization on every resolver rather than trusting a single JWT claim.