File Upload Vulnerabilities: The Complete Practical Guide
Content-Type and Magic-Byte Bypass
Bypassing upload type checks: spoofing the Content-Type header and defeating MIME sniffing by prepending real image magic bytes before your payload.
Published
Type checks are the softest defense on an upload, because the two things servers check, the Content-Type header and the file's first bytes, are both attacker-controlled. Spoof the header or prepend real image bytes and the file passes as an image while still carrying your payload. The catch is that passing the type check is only half the job, and the file still has to execute.
Spoofing the Content-Type header
When you upload a file, your request declares its type in the multipart part header. The server often trusts that value, so you just lie:
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/jpeg
<?php system($_GET['c']); ?>The part says image/jpeg, the content is PHP. A server that validates the declared Content-Type accepts it as an image. This is the first thing to try, because it costs nothing.
Why MIME sniffing still trusts the first bytes
A stronger server ignores your header and sniffs the real type by reading the file's leading bytes, its magic number. That sounds safer, but it only checks that the file starts like an image, not that the whole file is one. So you give it an image header and hide your code after it.
Prepending real magic bytes
Put a genuine image signature in front of your payload so the sniff passes:
GIF89a;
<?php system($_GET['c']); ?>GIF89a is the real start of a GIF, so the sniffer sees an image. The PHP after it is still there for an interpreter to run. Other common signatures work the same way:
GIF89a (GIF)
\x89PNG\r\n\x1a\n (PNG)
\xFF\xD8\xFF\xE0 (JPEG/JFIF header, then payload in a comment segment)The file is now a valid-enough image at the front and your code in the body.
Combining a spoofed type with a dangerous extension
Passing the type check does not make the server run your code. Execution depends on the extension and the handler, so pair the type bypass with a name the runtime executes:
filename="shell.php" with Content-Type: image/jpeg
filename="shell.phtml" with a GIF89a prefixThe type check sees an image, and the extension gets the file executed as PHP. The two techniques combine, which is the whole point: extension blacklist bypass is the other half.
When the server re-encodes and defeats this
The one defense that beats both tricks is re-encoding. If the server decodes your upload and re-saves it as a fresh image, your appended code is stripped out in the process, because it is not part of the pixel data. When that happens, a plain magic-byte prefix is dead, and you escalate to a polyglot or an image-parser bug that survives re-encoding. Knowing whether the server re-encodes tells you which rung to stand on.
Type checks fall fast, but only in combination with an execution path. Practice spoofing a type on a free live target, then pair it with an extension bypass.
← Back to File Upload Vulnerabilities: The Complete Practical Guide