Taking input from a user is tricky. It gets even more difficult when the input you're taking isn't just a string of text, but a file. There's a whole host of other security issues consider when you're planning on letting someone push their own file content up and onto your server. I wanted to write up a summary of things, a few reminders and maybe some things you hadn't thought about before, when it comes to handling file uploads in your application.
I've split it up into a few sections - the ones that I consider MUSTS that you absolutely have to do to be anything close to secure, the MAYBES that you can implement if it fits your situation (but don't have to) and the DON'T list. These are the things that you should never do in your file handling code.
<?php
$filename = 'test.txt';
if (is_file(realpath($filename))) {
$info = pathfinfo($filename);
echo 'Extension: '.$info['extension'];
}
?>
Obviously this is a pretty simple example and the pathinfo function only helps us so far. So, what's next that can help protect us from the bad guys and their evil file uploading ways?
<?php
$path = '/var/myuploads/test1.txt';
$mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE),'test1.txt');
echo 'MIME Type: '.$mime;
?>
In the above example, since the file we're working with is actually a text file,
we'd get back text/plain
. This way, even if we rename the test1.txt
file to something
like test1.jpg
, the mime type will stay the same. (You'll need PHP 5.3.x or later
to make native use of this functionality. If you're on an older version (shame on you)
, check out the PECL page for the extension.)
Validate the filename: Yes, I know this sounds similar to #1, but hear me out. When the user submits a file to be uploaded, it's just a POST request with some extra multipart field and content handling. Since it's mostly a text-based request, there's nothing saying that a malicious user couldn't fake one and set the name of the file to something to further their evil plans. Be sure that what they're giving you is a real filename and not something like:
"../../../tmp/test1.txt"
OR
"php://input"
Chances are, if you see a filename that's not really a filename, someone's messing with your form, trying to either make things break or extract as much info as they can. Be sure to watch for anything that doesn't look like a filename and filter it accordingly. You don't want to accidently call a move_uploaded_file and overwrite some key system file in the process, now do you...
Put uploaded files outside the docroot: As a genral rule, it's a really good idea
to move the files out of the /tmp
directory they're in and to a place designated for
user uploads. This definitely needs to be outside of the document root of the site itself
and can use a "gateway" script to pull in the file's contents and output it back to the
browser when requested.
If someone managed to slip a PHP file in on you and they had direct access to it from the browser, you'd be opening up a huge person-sized hole for anyone to walk through and gather as much information as they'd like about your application.
Specifically block dangerous files: This is sort of an addition to #1 and #3 but
a bit more specific than that. There are certain files, like an .htaccess
file for example,
that could cause some pretty bad damage if they ended up in the wrong spot. Be sure that
you filter this and other files that might directly effect the software the site runs on
and filter them out.
Check the referrer: Since it's not an all together reliable way to determine where the POST request is coming from - your site or another - it can't be considered a primary prevention method, but it can be helpful as a "first check" to be sure it's coming from the right place. Obviously, since the Referrer is something the user can change you can't trust it very far, but it can help cut down on spam submissions if you're getting bombarded from other sites.
Rename the files: Depending on your application, you might want to consider renaming the files once they've been uploaded. Using some sort of internal naming scheme not only prevents a user from directly accessing the file, but it can also help to make your app easier to maintain since you don't have to worry so much about the filename they give you anymore. Now your only worry is in getting the right extension for the file and tracking its relation to the user.
<?php
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="uploaded.pdf"');
readfile('source.pdf');
?>
In this example, we're working with what we've been told is a PDF file but we can't
be 100% sure. So, we take the (filtered, of course) information we know about the file
- it's name and the supposed Content-Type - and use the Content-Disposition: attachment;
header to force a download. This code would be placed in the proxy script you'd use
to pull the file data from a non-docroot directory (remember MUST #4).
There's a few other things to worry about when uploading files, some related to PHP specifically and how it handles its uploads, but that's a topic for another day. Hopefully this has been a useful list of things to consider when you're setting up your file uploads for your application. If you have any other suggestions, please leave them in the comments below - shared knowledge is better than walled gardens any day.
With over 12 years of experience in development and a focus on application security Chris is on a quest to bring his knowledge to the masses, making application security accessible to everyone. He also is an avodcate for security in the PHP community and provides application security training and consulting services.