File Upload Vulnerabilities: The Complete Practical Guide
File Upload Extension Blacklist Bypass
Bypassing upload extension filters: alternate executable extensions, double extensions, legacy trailing-dot and null tricks, and config-file drops.
Published
When an upload blocks .php, it is usually running a blacklist, and blacklists leak. The server maps more extensions to its interpreter than the filter knows about, it may compare names case-sensitively, and it can be tricked by double extensions or made to execute a safe-looking file by a config drop. The job is to find a name the app accepts and the server still runs.
Alternate executable extensions
A blacklist that blocks .php rarely blocks every extension the server hands to PHP:
shell.phtml
shell.php5
shell.phar
shell.phtDepending on configuration, several of these are executed as PHP (note that .phps is the opposite, a source-view handler that leaks your code instead of running it, so avoid it). Try each against the filter, then request the stored file to see which the server actually runs. The same idea applies to other stacks (for example .asp, .aspx, .ashx, .jsp, .jspx), so match the alternates to the runtime.
Case variation
If the filter compares extensions case-sensitively but the server's handler does not, change the case:
shell.pHp
shell.PHP
shell.Php5A check that blocks lowercase php but a handler that runs any case leaves this gap open. It is a long shot on modern stacks but free to test.
Double extensions
Some handlers decide how to run a file by matching a part of the name rather than only the final extension. On those, a double extension slips an executable type past a filter that only inspects the last one:
shell.php.jpg
shell.jpg.php
shell.php.xxxxxshell.php.jpg is accepted as a .jpg by a filter that reads the final extension, while a misconfigured handler that triggers on .php runs it as code. It is configuration-dependent, not universal, but worth a try against a last-extension-only blacklist.
Trailing dot, space, and null on legacy stacks
Older stacks mishandle characters at the end of the filename. These are legacy techniques, not reliable on current software, but still appear on old targets:
shell.php. trailing dot (stripped by old Windows, leaving shell.php)
shell.php%20 trailing space
shell.php%00.jpg null byte (old PHP < 5.3.4: truncates at the null, leaving shell.php)
shell.asp;.jpg IIS6 semicolon truncation (runs shell.asp)
shell.aspx::$DATA NTFS alternate data stream on IIS/WindowsTreat these as things to try against legacy software, and expect them to fail on anything patched this decade.
Dropping config files to change the rules
The strongest move against a strict allow-list is to not fight the extension at all, but to change what the server does with a safe one. Where the upload directory honors them, drop a server config file:
# .htaccess (Apache): make the server run .jpg as PHP
AddType application/x-httpd-php .jpg<!-- web.config (IIS): add a handler for .jpg -->
<configuration><system.webServer><handlers>
<add name="x" path="*.jpg" verb="*" modules="FastCgiModule"
scriptProcessor="C:\php\php-cgi.exe" resourceType="Unspecified"/>
</handlers></system.webServer></configuration>Now upload your shell as shell.jpg and the server executes it as PHP. This works when the upload directory allows these config files and the server reads them, which is more common than it should be.
Extension tricks are a toolbox: learn what the filter checks, then pick the cheapest name that still runs. When you can control where the file lands, combine this with path traversal in the filename. When the server re-parses the file as an image, escalate to polyglots. Practice slipping an extension past a filter on a free live target.
← Back to File Upload Vulnerabilities: The Complete Practical Guide