Pocket CTF Reference · Device home

False Identity

An archive accepts a modern asymmetric token and retains a legacy symmetric mode. Audit whether the verifier binds allowed algorithms to appropriate keys.

Objective

Before you begin

Complete Split Decision and retain the exact raw keyring bytes. Fetch the issued student token from /api/insane/token.

Concepts

RSA verification uses a public key, while HMAC authentication requires a secret shared key. A verifier must not let an untrusted token choose a mode that treats public RSA material as an HMAC secret.

An untrusted token reaches configured verifier policy, which permits either an asymmetric signature with a trusted public key or a MAC with a shared secret, before claims checks.

Token headers describe a proposed algorithm; they do not set verifier policy. An asymmetric signature is checked with the signer's trusted public key. A symmetric MAC is checked with a shared secret. Bind each allowed algorithm to the correct key type and trusted issuer, then validate audience, expiry, and application claims.

Suggested tools

Tool practice

Rehearse the worked tool exercise before using challenge data.

Base64url is a different alphabet from ordinary Base64. Decode header and payload as UTF-8 JSON, but treat the signature segment as binary. Preserve the exact saved verification-key bytes; pretty-printing that JSON changes bytes even if it preserves the same fields.

Investigation

Rehearse token serialization locally

A compact signed JWT has three dot-separated Base64url segments. The first two decode to JSON; the third decodes to signature or MAC bytes. The signed input is the ASCII text of the first two encoded segments, including their dot. Decoding a token does not verify it.

Practice encoding an unrelated JSON object:

import base64
import json

raw = json.dumps({"label": "lamp"}, separators=(",", ":")).encode()
segment = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
print(segment)  # eyJsYWJlbCI6ImxhbXAifQ
decoded = base64.urlsafe_b64decode(segment + "=" * (-len(segment) % 4))
print(json.loads(decoded))  # {'label': 'lamp'}

For an HMAC tool check, use hmac.new(b"practice-key", b"lamp", hashlib.sha256).digest() after importing hmac and hashlib. digest() returns 32 bytes; hexdigest() returns 64 printable hex characters. Encoding those 64 characters as the JWT signature is not equivalent to encoding the 32 raw MAC bytes. Recompute over the exact encoded segments you submit.

Observe

Decode the issued header and payload. Record alg, kid, role, and the exact key bytes returned by the first stage.

Form a hypothesis

Can an allowed legacy mode cause the verifier to use public verification material in the wrong cryptographic role?

Test carefully

Preserve the key identifier and compact token structure. Calculate locally, then submit one candidate as the documented form field.

Progressive hints

Hint 1

Decode header and payload as JSON and the signature as bytes. Compare the cryptographic roles of the accepted modes.

Hint 2

Compare the key type required by each accepted algorithm.

Hint 3

Preserve the issued token's compact serialization and key identifier.

Troubleshooting and reset

Base64url segments omit padding and use -/_. Do not parse and reformat the JWK before using its documented raw bytes. In-game hints reset with the session.