Allowing people to load content onto your server potentially allows them to deploy code which will be executed by your server. Your server can easily be pwned, leading to your work being destroyed/stolen and the website used for spamming, phishing, DOS attacks.....its a bad thing.
Where I've implemented file uploads, or have been asked how someone else should do it, I usually give this advice:
- Ensure that uploaded content is held outwith the document root, failing that, in a directory configured with "php_flag engine off"
- Only use filenames with safe characters - [a-z], [A-Z], [0-9], .-_
(OWASP, after detailling why you should not use blacklisting as a preventative measure, offer a black list of characters :) )
- Only allow content with a whitelisted extension to be uploaded
- Check the mimetype of uploads - using PHP's mime_content_type(), not the value supplied by the user
- Preferably convert the content to a different format (then back to the original format if required)
I am indebted to wireghoul for pointing out to me that there is a rather subtle attack which can be exploited for LFI on Apache systems.
Hopefully you know that the PHP interpreter will run the PHP code embedded in any file presented to it. To give a simple example:
- amend your webserver config to send png files to the PHP handler, e.g. by creating a .htaccess file containing
AddHandler application/x-httpd-php .png
- write your PHP code, and then append it to a valid png file....
echo '<?php mail("root@localhost", "hello", "from the inside");' >>image.png
- Point your web browser at the URL for image.png, and the picture renders in your browser, just as you would expect....but the PHP code executes too. No surprises so far. After all, if someone can reconfigure your webserver, they don't need to bother with the LFI - they've already pwned the system.
This comes about due to way mod_mime infers mimetypes from extensions
mod_mime will interpret anything in the name beginning with a . as an extension. From the linked documentation, note the paragraph
Care should be taken when a file with multiple extensions gets associated with both a media-type and a handler. This will usually result in the request being handled by the module associated with the handler. For example, if the .imap extension is mapped to the handler imap-file (from mod_imagemap) and the .html extension is mapped to the media-type text/html, then the file world.imap.html will be associated with both the imap-file handler and text/html media-type. When it is processed, the imap-file handler will be used, and so it will be treated as a mod_imagemap imagemap file.
The mod_mime documentation authors go on to explain that this is the default behaviour for handlers, but that it is possible to ensure that only the last extension is used for choosing the handler with a FilesMatch block around the SetHandler directive.
So if we name our uploaded file image.php.png then we should be able to get our PHP code executing on a server.
Indeed, guess what method PHP recommend for configuring your server? (as implemented by Redhat, Suse, Ubuntu and others).
Let's try it out!
In the earlier example, we appended the PHP code to the end of the file. When I tried this again with the double extension, I got a lot of unusual characters in my browser. The Content-type was PHP's default test/html not image/png. So instead, the PHP code needs to prefix the image and set the content-type header:
( echo -n '<?php header("Content-Type: image/png");
mail("root@localhost", "hello", "from the inside");
?>' ; cat original_image.png ) >> image.php.png
Point your browser at the URL, and once again, PHP code executes, the image renders but this time using the default config on most webservers.
The double extension is known but not by many.
It's not just me. I had a look around the web at advice being given about PHP file uploads - and a lot of it is wrong.
All the articles I've looked at claim that it only works where the extensions *other* than '.php' are unknown to the browser. As demonstrated here. this is NOT true.
Validating the mimetype won't protect against this kind of attack; although it would detect the file I created in the example, it won't work if the PHP code is embedded somewhere other than the start of the file. If it is contained in an unreachable block or as EXIF, then the code will execute, the file will be a valid image (based on its extension) and a mime check (which reads the first few bytes of the file) will say its an image. It will only render as an image when served up by the webserver if output buffering is enabled by default - but this will not often be a problem for an attacker.