Implementing Secure File Upload in PHP

Most PHP file upload scripts and content management system scripts (CMS scripts) require writable 777 permissions (rwxrwzrwz) to be set for certain folders for uploading photos and videos. Many security experts warn that setting 777 permission means that anybody can upload any content to your server, install malicious code, run unwanted programs and could potentially misuse your server. This is big security risk.

This article is also available here

Ironically if you implement a fileupload in your script, the upload wouldnt work for any other permissions other 777 or else your upload will fail. So you will be forced to set 777 permission for your writable folders.

Alternatively, to secure your server you can implement the following checks to in your PHP script as well as in your server. Remember if you are in shared hosting plan you might be limited in running as root.

The proposed first method is best suited for shared hosts and the second well suited for those who own your own servers with (dedicated or vps plan)

METHOD 1:

Using PHP Functions to Check Uploaded File

The first thing implement a secure file upload, you have to check the uploaded file for its size and type of file. Because your upload folder permission is 777, your site user are free to upload anything. It could also be a VIRUS!

1. Check for type of file upload and deny uploading other files.
2. Restrict its size.

If you are allowing your users to upload image files (jpg,gif,png) the trick is using getimagesize() function with PHP. If the uploaded file is really image file then it returns true as otherwise it fails. getimagesize() function returns width, height and type. Also dont forget to check for width and height of uploaded image file to restrict certain dimensions.

// check for uploaded file size

if ($_FILES['imagefile']['size'] > 50000 )
{
die ("ERROR: Large File Size");

}

//check if its image file

if (!getimagesize($_FILES['imagefile']['tmp_name']))
{ echo "Invalid Image File...";
exit();
}

// restrict width and height if its image or photo file

list($width, $height, $type, $attr) = getimagesize($_FILES['imagefile']['tmp_name']);

if ($width > 100 || $height > 100)
{
echo "Maximum width and height exceeded. Please upload images below 100x100 px size";
exit();
}

$blacklist = array(".php", ".phtml", ".php3", ".php4", ".js", ".shtml", ".pl" ,".py");
foreach ($blacklist as $file)
{
if(preg_match("/$file\$/i", $_FILES['userfile']['name']))
{
echo "ERROR: Uploading executable files Not Allowed\n";
exit;
}
}

Should you need to deny uploading of upload files, you can create a blacklist of files in an array and loop over to check the header. Remember not to trust browser header and the headers could also be easily faked.

NOTE: Above getimagesize() and checking for blacklisting the uploaded files, both the methods can be bypassed. More information about vulnerabilities here.

Generate Random File Names

When you place any uploaded files in your upload folder, rename the file to some random names and track the filename in the database. It can be of md5() hash or any randomly generated numbers or string.

Upload Folder outside WWW Root

The simple way is to secure your contents is moving your folder outside WWW root. If you run Cpanel its public_html and in plesk its httpdocs. In this way the contents of your writable folder will not be revealed to outside public. Remember it is still writable.

Inside your PHP script you can access the folder something like to the folder above your WWW root

 (single dot)

Still, anybody could upload a malicious sript and run on your server. For that place a .htaccess file inside your uploads folder to disable CGI execution. This works well if you are in shared hosting plan

Disable Script Execution with .htaccess

Just create .htaccess file with contents below and place it on the uploads folder to disable running malicious scripts.

AddHandler cgi-script .php .php3 .php4 .phtml .pl .py .jsp .asp .htm .shtml .sh .cgi
Options -ExecCGI

Disabling executing of these files could give us an extra layer of protection.

Further if you are allowing your users only photos or picturer, you can restrict other files by placing the following code your your .htaccess file.

order deny,allow
deny from all

How to Read & Display the Files?

Remember once you have moved the folder outside the root, the best way of outputting the files (i assume images) is write a PHP script (call it getimage.php), read the file and send desired headers to the browser. See the example below. I am assuming that you have a image file in the upload folder and the method for reading is below…

// this is just example only 

$imgfile = $rsPhoto['photo']; // or value from database 

list($width, $height, $type, $attr) = getimagesize($imgfile); 

switch ($type)
{ 

case 1: $im = imagecreatefromgif($imgfile);
header("Content-type: image/gif");
break; 

case 2:
$im = imagecreatefromjpeg($imgfile);
header("Content-type: image/jpeg");
break; 

case 3:
$im = imagecreatefrompng($imgfile);
header("Content-type: image/png");
break; 

} 

METHOD 2:

The best way of handling file uploads securely is rather than giving writable permissions to users, is to allow the writable permission to apache itself. In this way the apache server has writable permission rather than the user. Just chown the writable folder to apache or nobody and assign 770 permission.

In this way the public has no access to read or write or execute permissions in the uploads folder. You will notice that apache has rwx and so as the owner. You can safely place the upload folder inside www folder without any concern.

chown -R apache uploads
chmod -R 770 uploads

If anybody tries to access the uploads folder, through URL you will see forbidden. Because apache is the group owner you will have no problem in displaying the images or photos to the browser.

This method works best if you have your own dedicated or vps plan with root permissions.

Using SuPHP / PHPSuExec

If you have suphp compiled with CGI version of PHP you might be able to run PHP with server previleges for writing upload folder. This is also another method. You can download suphp free for download. This is another workaround for the above method.

Conclusion:

Remember no system is 100% secure. I have presented here the best methods, known to me in terms of securing the server. I am always very much worried about assigning 777 permissions to the writable folders and after lots of discussions in the forums and little research on the Web, i felt that i needed to share the knowledge i acquired in tackling 777 permissions problem.

Similar Posts:

Tags:

Prabhu Balakrishnan

I am cool and friendly internet entreprenur blogging since 2005. My interests are wordpress, PHP, linux servers, SEO and travel. Besides computers, i am crazy for video games, travel and a cup of hot coffee! I am born in Coimbatore, India but i currently live in Budapest, Hungary. Feel free to contact me folks!

Leave a Reply

Your email address will not be published. Required fields are marked *


seven − 2 =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please wrap all source codes with [code][/code] tags. Powered by
  1. DrTebi

    Hello,

    sorry for blowing this out, but I don’t think your methods are very efficient. The problem is using PHP to “serve” an image. This takes up a lot of resources, which Apache could just do without PHP’s assistance.

    An easy way to prove this is with Apache’s benchmark tool “ab.” Let’s say the the PHP script spitting out the image is http://example.com/photo.php?id=1:

    ab -t30 http://example.com/photo.php?id=1

    Wait 30 seconds and note the results. Then place the image itself into another folder, so that it can be served without any PHP processing necessary, and run “ab” again, e.g.:

    ab -t30 http://example.com/static/photo1.jpg

    You will see a tremendous difference in the benchmark results. That is so because in the first example Apache will have to start the PHP interpreter for every image request, and that eats up resources.

    This might not be a big deal if you have a few hundret visitors a day, but if you have a few thousand or more, it’s going to bring your server to it’s knees.

    A solution to avoid the “777” directory permission, at least on a dedicated server where you have root access, is to leave these directories publicly accessable, set the permissions on those directories to 775, and chown to user1:apache (assuming you are “user1″ and apache runs as “apache”). For example:
    drwxrwxr-x 12 user1 apache 512 Aug 23 19:42 img

    I believe this is a good solution, but correct me if I am wrong. I have picked this up from here:
    http://www.nabble.com/httpd-folder-is-owned-by-root-td18388559.html

    DrTebi

    Reply
  2. pbu

    very true!
    Setting permissions to 775 is an efficient method for images folder but dont forget that you have to hide the directory browsing using apache htaccess

    Reply
  3. DrTebi

    Yes, disallowing directory browsing should be enforced as well. I actually have it disabled system-wide, and enable it if I see a need for it.

    I have found an interesting and quite comprehensive document that explains all the problems with image uploads, if you’re interested, you can find it here:
    http://www.scanit.be/uploads/php-file-upload.pdf

    Still… my suggestion works very well. But after reading this document, I figured that image uploads can still cause problems, if they have some hidden PHP code in it. But there is yet another simple solution: just turn off the PHP engine for the image directory. This can be done within a directory container block in httpd.conf:

    php_admin_value engine Off

    or in an .htaccess file within the image directory:

    php_value engine Off

    Have a nice day :) DrTebi

    Reply
  4. stephen chan

    Hi!
    If a website is shared-hosted and have no control over Apache setting, and there are no specific file type that is allowed or not allowed, what can I do?

    By the way, is there anyway where I can build in a virus checking (triggering some anti-virus software to check the file to be uploaded) mechanism.

    Thanks,
    Stephen

    Reply
  5. pbu

    In that case the only option you have is to make folder writable 777. however you can also hide the contents of folder and disable exec permissions with htaccess file

    Reply
  6. Carol

    Hi,

    I think rather than adding htaccess its better to add the codes in httpd.conf as

    Options -Indexes
    Options -ExecCGI
    AddHandler cgi-script .php .php3 .php4 .phtml .pl .py .jsp .asp .htm .shtml .sh .cgi

    bcoz .htaccess has some performance issues.

    thanks
    Carol

    Reply
  7. Kaushik

    Hi there,
    awesome tips. Another layman query though, this is actually more wordpress specific, I use a shared host and have to necessarily give the 777 permission on uploads. the issue though is more of the ownership. I understand it is possible to do the chmod and chown commands through php itself, any light on this? the main issue for me is that my shared host assigns the username as user and group id, but when wordpress connects to the file it uses nobody i.e 99/99 . Any thoughts on if/how this can be controlled? Thanks in advance

    Reply
  8. Hitesh

    Nice information.. really helpful.
    Well this can help you in secure file writing through ftp in php
    http://phpwala.wordpress.com/2009/11/15/how-to-write-file-though-ftp-in-php-without-777-permission/

    Reply
  9. pbu

    Dont allow users to upload photos in GIF photos as it may contain harmful code. Allow only JPG and PNG

    Reply
  10. Fap Turbo Discount

    thanks for the great help for the beginner! Such a nice site for me… Thanks for the great info! You give more knowledge about the path I will going through. Hope to see this site successful in the near future.

    Reply
  11. gal

    i actually handled it a bit differently.
    images are uploaded for specific causes, like user profile image, so i don’t need to allow a complete directory file management.

    * the file is uploaded to the temp directory.
    * i’m checking the mime type of the file.
    * i’m not copying the file to the destination folder. instead, i use the gd2 library to create a similar looking image using imagecopyresampled.

    any thoughts?

    Reply
    • pbu

      It can put extra load on your server, if you too much use image processing library or if too many clients upload images at same time.

      Reply
  12. apexsol
    ?php  
    if($_FILES['userfile']['type'] != "image/gif") {  
    echo "Sorry, we only allow uploading GIF images";  
    exit;  
    }  
    $uploaddir = 'uploads/';  
    $uploadfile = $uploaddir . basename($_FILES['userfile']['name']);  
    if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {  
    echo "File is valid, and was successfully uploaded.n";  
    } else {  
    echo "File uploading failed.n";  
    }  
    ?>  
    
    Reply
  13. Toby

    Checking for a file extension is not secure – it could be renamed.

    Checking a header / content type is also not secure – a malicious script could also contain a jpg header and appear / behave like an image.

    Reply