10 Things you can do to protect file uploads via PHP on your Linux / Apache web server

This small list of upload protections is geared towards a LAMP environment (Linux Apache Mysql PHP). Each web server and or language will have similar traits, but the tips listed below may not apply depending on your situation.

1. Don't allow uploads. Sorry, that I even need to say this, but if your application doesn't need upload handling, then configure the server to disallow the upload entirely server-side. This prevents the possibility of harmful scripts (test apps, free ware, etc) that could be exploited in the future.

2. Check MIME type server-side. Don't rely on $_SERVER['file']['type'] or by simply checking the file extension in the name! The 'type' should be untrusted since that is defined by the browser and can be easily spoofed. It's much better to allow the upload to /tmp and then use a server-side process to check the MIME type using `file` and comparing that to your allowed MIME type list. This can easily be done by using PEAR's fileinfo package using finfo_file(). Also, if you're expecting images only, you could also optionally use exif_imagetype() to extract image information. I personally like using exif_imagetype() vs. getimagesize().

Here is an example of exif_imagetype() use:

$imgTypeConstants = array(
IMAGETYPE_GIF,
IMAGETYPE_JPEG,
IMAGETYPE_PNG,
IMAGETYPE_BMP,
IMAGETYPE_TIFF_II,
IMAGETYPE_TIFF_MM);
if($checkImage && function_exists('exif_imagetype')){
foreach($imgTypeConstants as $constantVal){
if(exif_imagetype($_FILES[$fieldName]['tmp_name']) == $constantVal){
$IMAGE_type_check = true;
break;
}
}
$MIME_type_pass = ($IMAGE_type_check ? true : false);
}

Here is an example of using finfo_file() -- PHP5 has different usage, this is based off of PHP4

if(version_compare(substr(PHP_VERSION,0,1),5) == -1){
$res = finfo_open(FILEINFO_MIME);
$tmpMIME = finfo_file($res,$testFile);
$tmpArr = explode(";", $tmpMIME);
finfo_close($res);
if($tmpMIME == "image/jpeg"){
echo "good";
}else{
echo "bad";
}
}

3. Use mod_security to validate file uploads via a approver script. Mod_security has a great rule for inspection:

SecRule FILES_TMPNAMES "@inspectFile /path/to/approver.sh"  "t:none"

With this Apache, will pass the file to the approver script and that script would access `file` and return mime types. If the approver script returns a true, the file is allowed to be passed to PHP, else the file is optionally removed and the transaction ends. Along with inspectFile, you could use mod_security to block file uploads entirely on the server, but open up certain directories or vhost to have the ability for uploading files.

The approver script can be written in any language (if interpreter is available), there are a few examples in perl or bash on the web.

More information about mod_security can be found here:
http://www.modsecurity.org/

4. Use suhosin. Suhosin was the hardened-php project. They've added their hardened-patch, but also using the suhosin PHP module, you can increase security on your PHP applications. In regards to PHP and uploads, you can do things similar to mod_security with the use of approver scripts. They also have verification handling in their suhosin.ini file:

suhosin.upload.verification_script = /path/to/approver.sh

Suhosin also allows adds some simple checking depeding on your upload needs:

suhosin.upload.disallow_elf = 1
suhosin.upload.disallow_binary = 0
suhosin.upload.max_uploads = 25

With these you can disallow elf executables, or prevent the upload of binary files (text only). Suhosin can also be set at the vhost level, so you can crank down certain vhosts, or loosen restrictions depending on your give situation.

More information about suhosin can be found here:
http://www.hardened-php.net/suhosin/

5. Don't upload to a web accessible directory. Meaning, don't have your upload script upload a file to a directory that can be accessed with a web browser (http://www.yourdomain.com/images/your_fresh_upload_file.jpg). If a malicious file was successfully uploaded, they'll need the ability to execute the script, hanving that above the web root will help. Obviously, someone with skills will still be able to execute it.

To display the image (ie: photo gallery, etc), use PHP to fopen(), fread() the file and present it to the browser using header().

Another option similar to this is to store the image data as a BLOB in mysql, this still might have issues since a malicious file could be saved into mysql, so verifying what you want before displaying would be wise.

6. Pay attention to permissions. Depending on how you set up your web directories, make sure permissions are properly set up so the Apache user won't have the ability to write/delete files in other directories. I've seen a few web set ups where all vhosts were owned by the Apache user, this would give the attacker full reign of all web sites.

7. Don't serve certain file types if you need to have a web accessible upload directory. If you need to have the directory below the web root, you can use Apache directives to not serve PHP files for example you could use something like this:

<Directory /var/www/path/to/upload/directory/>
<Files ~ "\.php$">
   Order allow,deny
   Deny from all
</Files>
</Directory>

8. Make sure you /tmp directory is not executable. Depending on your set up, files will be loaded in /tmp, and this is a common place for attackers to drop their initial scripts. Making this partition non-executable will stuff those script attacks. Again, against a skilled attacker, they'll try to execute the script in different location after their initial failure, but this could help prevent automated attacks.

To make /tmp non-executable add this to your fstab. Along with /tmp, it's good to do the same to /dev/shm.

/dev/hda3               /tmp            ext3        loop,rw,nosuid,noexec   0 0
none                    /dev/shm        tmpfs           nodev,nosuid,noexec     0 0

9. Chroot your Apache environment. This doesn't protect your uploads, but protects the Apache environment if harmful files were uploaded and executed. You can do this with mod_security, since this has chroot handling, or you can use mod_chroot, or create a custom chroot jail for Apache by hand. This all depends on your skill level and threat needs.

10. Use safe_mode and open_basedir. There are lots of debate about this, but using these as a preventative measure can help in the big picture. One nice thing with safe_mode is the UID restriction. If a PHP file was uploaded, it will be owned by Apache, and hopefully your application (all files related to it) will be owned by a different user. This can cause some complication on script execution since UIDs are not matching. open_basedir locks the access to set directories only, so files outside of this restriction are inaccessible. Again, a skilled attacker could use php:// or cURL to work around this, and these can also be countered via suhosin or hardening your php.ini.

The bottom line is there are some basic things you can do to protect your application or server. Not one of these is the answer, and all combined is not the answer. They are layers that can slow an attacker down, cause log file entries, etc that can be used to detect a attack. When you're accepting uploads, you're also accepting the fact they individuals may try to take advantage of that upload. Make a target difficult, will usually push someone away, since there are much easier targets out there.

Hello! It is cool! My name is e11392d82