Secure Your Upload Methods in PHP

By merly on April 16, 2013

In most of our web applications, developers provide upload file functionality such as image upload, for example. This functionality could be exploited by attackers to upload malicious “Web shell” code, which might give them command-prompt access to the server.

Here is an example HTML code for upload.

<form name=”imageupload” action=”upload.php” method=”post”>

Upload File<input type=”file” name=”file” >

<input type=”submit” name=”upload”>

</form>

When this is implemented by developers, no verification of the uploaded file is done in most cases. The form given above invokes an upload.php which is a PHP script that process the upload functionality. The code in upload.php might move the file to a common/well-known folder, without verifying its content — like what’s given below:-

<?php

$uploaddir = ‘uploads/’; // Relative path under webroot

$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”;

}

?>

Now assume that the attacker is trying to upload a file containing code as follows:

<?php

System(“ls”);

?>

In such a case, upload.php will move the attackers’ file (named uploadimage.php, for example) to the uploads subdirectory. If, for instance, the attackers then enter http://site/uploads/uploadimage.php as the URL in their browser/client, they will get a listing of the current working directory.

Similarly, they can upload other PHP scripts with different shell commands, or even include code to download and run a back-door program, giving themselves a direct command shell on the attacked system.

Precautions

We could do the following precautions to avoid these types of attacks.

a)  Content-type verification

If a form upload with a non-image file (say, the uploadimage.php script seen above) is received, the HTTP request could be something like what’s shown below (note the Content-Type header):

POST /upload.php HTTP/1.1[…]

Content-Type: multipart/form-data; boundary=xYzZY

Content-Length: 156

–xYzZY

Content-Disposition: form-data; name=”userfile”; filename=”uploadimage.php”

Content-Type: text/plain

 

You could check the content type of the file that is being uploaded by adding the following validation code:

if($_FILES[‘userfile’][‘type’] != “image/gif”) {

echo “Sorry, we only allow uploading GIF images”;

exit;

}

This new code will check the uploaded file’s content/MIME type that is, GIF and block any others. However, this content-type header is sent by the client doing the upload, and attackers could easily forge a false content-type header while actually uploading a PHP script. The present upload.php will still accept the file; the vulnerability is not fixed.

b) Image File content verification

The next precaution a developer can take is to check the actual content of the uploaded file, to verify whether it actually is an image or not, using the PHP function getImageSize():

$imageinfo = getimagesize($_FILES[‘userfile’][‘tmp_name’]); //check image size

if($imageinfo[‘mime’] != ‘image/gif’ && $imageinfo[‘mime’] != ‘image/jpeg’) {

echo “Sorry, we only accept GIF and JPEG images\n”;

exit;

}

The function getImageSize() returns the image’s size, its image type and MIME type, if the file is a valid image file (else it will generate an error). This measure will defeat Content-Type header forgery, but it is still not safe; the attackers can embed malicious PHP code inside a GIF image file, rename it to uploadimage.php, and upload it.

When getImageSize() looks at that file, it extracts data about the first portion, which is a valid image. However, the PHP interpreter, when invoked on the file as before, executes the PHP code in it, along with the binary data! Hence, if you don’t do filename verification, you have not made this upload any safer!

c) File name verification

The final check is on the extension of the uploaded file’s name, as in the following code:

The final check is on the extension of the uploaded file’s name, as in the following code:

$blacklist = array(“.php”, “.phtml”, “.php3”, “.php4”);

foreach ($blacklist as $item) {

if(preg_match(“/$item\$/i”, $_FILES[‘userfile’][‘name’])) {

echo “We do not allow uploading PHP files\n”;

exit;

}

}

Here, $blacklist contains a list of file extensions, and the preg_match() function applies them as a regular expression check against the uploaded file’s name. These are all the extensions that the Web server is configured to accept as PHP executable files. Once you block files with these extensions, even if the attacker uploads PHP code in files with other extensions, the PHP interpreter won’t execute them and so the attacker is blocked.

Other issues concerning a file upload

Other than the above safeguards, developers should also consider the following:

  • There has to be a limit on the size of the file that is being uploaded, lest it consume all the available HDD space on the server, causing a failure/denial of service.
  • Developers have to take care that uploaded files are not easily or directly viewable by users or attackers (i.e., they should not have a well-known URL for uploaded files, such as the uploads folder mentioned earlier). It is best if uploaded files are stored in a folder that is not below the Web root. Also, developers could store the original file name (as uploaded) in a database table, and rename the file in the storage folder with a randomly generated name, storing that alongside the original filename in the database. Then, the application can present the user with the original name from the database, but stream the contents from the renamed file.

Leave a Reply

Contact us!
SCROLL TO TOP