The Challenge
The server accepts image uploads and serves them from an uploads/ directory. It validates the file type by checking the magic bytes at the start of the file — specifically the GIF signature. The upload path is not isolated from the PHP interpreter.
Approach
A GIF polyglot starts with GIF89a (6 bytes) which makes getimagesize() and magic-byte checkers accept it as a valid GIF. Append a PHP shell on the same line after a semicolon. PHP’s parser ignores the GIF89a; prefix as it is not valid PHP syntax and proceeds to parse the <?php ... ?> block.
After upload, the response HTML contains the uploaded filename in a pattern like uploads/TIMESTAMP_flag.php.gif. Requesting that URL directly causes the PHP interpreter to execute the embedded shell — the server runs PHP against any .php in the name regardless of the final extension.
Solution
|
|
The filename flag.php.gif lands in the upload directory. The server returns the timestamped path in the response HTML; re.findall extracts it. A second GET to site + newUrl triggers PHP execution and system("cat /flag.txt") prints the flag.
What I Learned
Magic byte validation alone is not sufficient for upload security. The file must be served from a directory that does not execute PHP, and the server must never infer a MIME type from the file content alone when the file will be served as-is. Double-extension files like .php.gif exploit misconfigured Apache AddHandler directives.