Challenge: Spaetzle · CTF: GPN CTF 2026 · Category: Web · Difficulty: Easy
Summary
index.php exposes md5_file($_GET['path']) and file_get_contents($_GET['path'])
but only ever prints the MD5 — the file content is never returned. PHP stream
wrappers are allowed, so the hidden read is turned into a file disclosure with the
error-based PHP filter-chain oracle (memory-exhaustion as a boolean), leaking
/flag byte-by-byte.
Solution
Step 1: Recon — an MD5-only oracle over arbitrary paths
The homepage throws md5_file(): Filename cannot be empty and
file_get_contents(): Filename cannot be empty, then prints d41d8...e (md5 of "").
Fuzzing parameters finds the input: path. The response is only the 32-char
hash from line 21; the file_get_contents result on line 23 is read but never echoed.
Stream wrappers work — data://text/plain,test returns md5("test") — so
php://filter chains are in play.
Step 2: Leak files via the error-based filter-chain oracle
Even though content is never displayed, a convert.iconv filter chain leaks each
base64 char of a file by triggering Allowed memory size of ... exhausted for a
matching byte. That distinguishable response is the oracle. The app reads $_GET
only (POST is ignored), so the chain length — and thus reachable offset — is bounded
by Apache’s LimitRequestLine; the flag is short enough to fit. The script below
finds the flag with the md5 existence oracle (/flag is real, in-webroot flag.txt
is a decoy), leaks it, and completes/verifies it against md5_file("/flag").
| |
Output:
| |
Flag
| |