Publishers of technology books, eBooks, and videos for creative people

Home > Blogs > Securely Handling File Uploads: Five Critical E-Commerce Security Tips in Five Days

Securely Handling File Uploads: Five Critical E-Commerce Security Tips in Five Days

A feature of many of today's Web sites is the ability for users to upload files to the server. While often necessary, this process presents a new type of risk to servers and sites, whether any user can upload a file or just an administrator can. In this post, I explain what steps you can take to limit the risks of allowing for file uploads.

There are three areas in which you can improve the security of a file-upload system:

  • Limiting who can upload files
  • Limiting what types of files can be uploaded
  • Limiting access to the uploaded files

I'll discuss these three approaches in order, but on most sites you'll want to use all three for ultimate safety. First, though, let's think about what the real security concerns are when it comes to uploaded files.

A minor concern is that a user puts inappropriate content on a site. This could be risque media or illegal copyrighted material. Although such things should be prevented, they aren't truly dangerous. A more serious issue involves a hacker uploading a virus, trojan, or script to a server, which can then be used for all sorts of mayhem. In order to pull that off, the hacker must get the file onto the server and then have it be executed. A sound security approach, therefore, prevents one or both of those actions.

To start, limit who can upload files by simply restricting this capability to identified users. Using sessions or cookies, limit access to any page that allows for file uploads to users that have logged in. Restricting who can do what is the first security approach any site should take. If access cannot be well limited on your particular site, then be extra careful to follow my subsequent recommendations.

Limiting what types of files can be uploaded is critical, as some file types are dangerous (like scripts), whereas others are not (like images and PDFs). Thankfully, there are very few situations in which all types of files should be allowed: YouTube only accepts video files; Flickr, images. So identify only those acceptable file types your site can take and add code comparing the uploaded file type to the allowed types.

To identify the type of file uploaded, checking the file's extension is an easy test and always worth doing:

// Allow only image files:
$ext = substr($file['name'], -4); // Get the last four characters.
$allowed = array('.gif', '.png', '.jpg', 'jpeg');
if (!in_array($ext, allowed)) {
    echo 'The file is not an allowed type!';
    $problem = true;
}

Checking the file's MIME type, provided by the browser upon upload, is also easy:

// Allow only PDFs:
if ($file['type'] != 'application/pdf') {
    echo 'The file must be a PDF!';
    $problem = true;
}

Unfortunately, neither of those approaches is foolproof, as a hacker can make a file of one type appear to be, or actually claim to be, of another type. The above checks are still useful, as they'll catch benign erroneous uploads, but more stringent validation can be done using PHP's Fileinfo extension. This extension was previously part of PECL, and added to the PHP core as of version 5.3. The Fileinfo extension looks at the actual file data to determine its type based upon the presence of so-called "magic bytes" (also known as the "magic numbers" or "file signatures"). For example, a GIF image file begins with the ASCII code that means "GIF87a" or "GIF89a". A PDF begins with "%PDF".

To start, create a fileinfo resource:

$fi = finfo_open(FILEINFO_MIME_TYPE);
The first argument is a constant representing the type of information you're looking for.

Next, apply the Fileinfo resource and the finfo_file() function to a given file:

$mime = finfo_file($fi, $file['name']);
Now you can close the Fileinfo resource:
finfo_close($fi);

Finally, compare the value of $mime to the allowed MIME types, as in the code posted earlier.

As a side note, you can (and should) also limit the maximum size of an uploaded file, although this doesn't really impact the security of the site (as a 1KB script can do more damage than any 1MB PDF).

The third security measure is limiting access to uploaded files. There are three ways of doing so.

  1. Store the file using a custom name
  2. Limit direct access to the file
  3. Limit any access to the file to recognized users

Looking at these in reverse order, if at all possible, limiting access to uploaded files to only registered users again limits the security concerns involved with file uploads, in the same way as limiting the ability to upload files does.

Second, ideally uploaded files should be stored either outside of the Web root directory or within a restricted public directory. By using either technique, direct access to the uploaded files is denied. (Also, the security concerns of having a directory with world writable permissions are limited this way.)  But how, you may be wondering, can the files be made available after doing this? The answer is to use a proxy script. A proxy script is a file written in PHP or any other server-side technology that acts as a proxy for a resource. The proxy script can do any necessary validation and then send the file to the browser. Here is the appropriate PHP code for doing that:

//Send a Content-Type header:
header('Content-Type:application/pdf');

// Send a Content-Disposition header:
header('Content-Disposition:inline;filename="somefile.pdf"');

// Send a Content-Length header:
$fs = filesize(‘files/somefile.pdf');
header ("Content-Length:$fs\n");

// Send the actual file:
readfile('files/somefile.pdf');

As you can see in that code, for maximum compatibility, the proxy script should indicate the file's content type, size, and name, before passing along the file itself (by using the readfile() function).

The third access-related security measure is to store the uploaded file on the server using a custom name. The ID of an associated database record is a logical choice, or you could use a hash of the original file name. In either case, you can store the file on the server without a file extension, as the extension is only meaningful when a file is being executed, and these files are specifically meant not to be executed (but rather served). When using a proxy script, you can provide the original file name back to the browser.

By following the techniques outlined in this post, you can securely limit who uploads what kinds of files to your server, store the files on your server securely, and then restrict who can access those files and how. By doing all this, you'll improve your site's overall security.