In the series of tubes there’s a ton of tutorials and guides on image upload security, but apparently still not enough – I stumbled upon yet another PHP application image upload vulnerability. These are ridiculously easy to spot, take about 10 minutes to exploit and if the attackers succeed, odds are they will be able to upload a shell, execute arbitrary code on your server and do pretty much whatever they want. So if you’re a developer (especially PHP!) and don’t know how exactly image uploads should be implemented, please please please take the time to read and understand.
Whitelist and validate mime types
Google up a proper list of image mime types (image/jpeg, image/png and so on), make a whitelist and validate the uploaded file. You’ll also want to make sure you actually serve the uploaded file with the same mime type.
Make sure the file is an image
Attackers can easily manipulate the uploaded file’s mime type. You will need to check if the uploaded file is actually an image.
Whitelist and validate uploaded file extension
Sadly, a lot of developers appear to think that “hey, if it’s the correct mime type and it’s an image, we’re good to go.” Nope. A .gif or .jpg file can be a valid image file and still contain metadata (i.e. any text). So the picture of a kitten I just uploaded on your server probably also contains something in our favorite programming language.
You will need to whitelist and validate the uploaded file’s extension. Make sure you validate only the last part after a dot, because I’m going to try uploading stuff like shell.jpg.php and so on. Also make sure it matches the mime type.
Steps 1-3 are the basics. If you don’t follow them, I will personally hunt you down, upload a .php file to your server and use your CPU to mine bitcoins.
Is this secure enough?
If you’re checking the uploaded file’s mime type and extension against a whitelist, making sure it’s an image and not allowing anything else, the attackers can upload as many images containing PHP code (or other exploit) as they want, but they can still only request a .jpg file from the server anyway.. so nothing bad can happen. Right?
Well, not really. There are a lot of other interesting exploits out there, many of them should be fixed in most systems but not all. What come to mind are the PHP null byte exploit (uploading file.php%00.jpg – in some situations the server will ignore everything after a null byte), the .gif that’s also a valid .jar, flaws in image processing libraries, and so on.
Allowing people to upload random files to your server is serious business, so you’ll want to have multi-layer security and work under the assumption that one of the layers will fail. Plus, even if everything else is secure, people will still forget things, make mistakes, copy-paste server configurations without understanding what they actually do and so on.
If possible, resize the image and strip metadata
Stripping the metadata and resizing the image will make sure anything funky inside the image file will be destroyed. Delete the original uploaded image. Don’t keep it anywhere publicly accessible.
Rename the whole file to something static and alphanumeric. A random md5 hash is fine, for example. Store the user-specified file name in a database table, if necessary.
You can also change the file extension to whatever it should be according to its mime-type.
Upload your images to another server that only serves static content
If the image server doesn’t have PHP installed, it will be remarkably more difficult to exploit using uploaded PHP files. And even if something goes horribly wrong, the attacker will only have access to your static uploads (as opposed to everything).
Amazon S3 is probably cheaper than anything else you might be using anyway.
Keep libraries up to date
If you’re using an old and vulnerable version of ImageMagick, you’ll still be screwed. Keep your software up to date.
Apply your brain.
Security depends entirely on context. You might do everything I’ve outlined so far, but if your app has any not-so-generic functionality, it’s possible that this isn’t enough.
So have a critical look around your code base and remember not to trust any user input anywhere at all.
Test. Have someone hack your app.
If you’re only looking at your code from a developer’s perspective, the odds of missing an obvious flaw are quite big. Sloppy or hasty coding can undo all the effort you put into securing your app.
So you’ll need to test your app. You can do it yourself, but you’ll be a lot better off finding someone who knows about security and having them test it. You as a developer already have a mental model of the upload process and make assumptions about how your code works, and how it works together with external libraries. A fresh pair of eyes is always good – someone who doesn’t make these assumptions can find something you’ve missed.
Backup and monitoring
The best piece of advice applicable for any web application: assume you will be hacked. Backup everything, practice restoring stuff from backup, monitor your app and test the backups.
And remember: Security is difficult. Always test.
* I’m using the word “bulletproof” only to attract your attention. The security of your image uploads depends on a ton of other factors, including the security of your whole web stack (that includes any external libraries used by the server, programming language etc). And these definitely contain some not yet found vulnerabilities. There is no such thing as “bulletproof security”, just “too hard to exploit at the moment.”