The ultimate solution for the unrestricted file upload

blog + secure coding + Systems security + Website security Z. Oualid today

Background
share close

As an old developer and a penetration tester, between all the vulnerabilities I have seen in my whole experience in both fields file upload was the most difficult one to fix and the most dangerous. The difficulty resides in the number of things you should check before allowing a file to get into your server. Therefore, how to prevent file upload vulnerability in PHP?

Here is a list of the things you should do to fix the file upload vulnerability:

  • Check the file type by yourself.
  • Check the file extension.
  • Perform a malware scan
  • Create a unique name for the files
  • Check if the file already exist
  • Remove execute permission from the upload folder
  • Remove write permission on the other files and folders
  • Limit the file size to a maximum value
  • Restrict small size files
  • Try to use POST method instead of PUT (or GET!)
  • The compressed file should be checked one by one as a new file.
  • File uploaders should be only accessible to authenticated and authorized users if possible.
  • Ensure that uploaded files cannot be accessed by unauthorized users.

I have decided to make this blog post to resume all the things you should do as a developer to fix this issue with PHP code examples. In addition, I want it to be like a reference for developers while fixing this issue. So if you are interested to learn more about this subject just keep reading.

In all the next source codes, the $file variable is actually equal to $_FILES[‘variableName’] the default PHP variable that contains all the file information once it is sent by the users. Therefore, for the sake of simplicity, I will only use $file.

This blog post is divided into two big parts depending on the level of security you want to get to. The basic file checks are the minimum checks you need to perform on the uploaded files to avoid the unrestricted file upload vulnerability. In addition, the advanced security check and controls explain what else you can do to enhance the security level of your application.

Basic file check

File size check

The first thing you need to check before performing actions on the file once the user sends the file is to look at his size. If the size is so big then the next checks would be useless and could cause a denial of service. Therefore, to check the file size you can use the following code snippet:

if ($file['size'] >2000000) // the value here is in octet so 2Mo
{
	Return “error, big file”;
}

File type and extension check

This check is very important and must be performed in the backend. However, it is not 100% reliable, as it is very easy to bypass. The reason behind this is that the function responsible for the check of the file type relies on the magic numbers of the file. Unfortunately, those numbers could easily be altered or forged to pass malicious files. Therefore this check should never be the only security check you do to the file.

Let’s suppose you only want to allow PDF files for example in the case of CV uploader. Therefore, here is the MIME you need to look for in the uploaded files (application/pdf)

Here is an example of such a check:

$finfo = new finfo(FILEINFO_MIME_TYPE);
$accepted_file_types = array(‘pdf’ =>“application/pdf”);
If(!in_array($finfo->file($filename), $accepted_file_types) )
{
	return “Error: File type not allowed”;
}

I have created an array of the accepted file types with an extension key to force the file to use my own extension rather than the one he is sending. This will avoid Magic number bypassing as this will impact the extension. The code I have written here combine then two checks (file type and the file extension)

Create a unique name for the files

It is recommended to change the file name of the uploaded file before storing it in the file system. This change protects you from any file destruction or replacement that could happen by uploading a configuration file like .htaccess or something like this.

It is also recommended to use a powerful hashing algorithm to avoid any collision problems.

Here is a code snippet that you can use to generate the file name:

$filename = hash('sha256', $file['tmp_name'] . date("Y-m-d h:i:s"));

If you want to add more checks to avoid the hash functions collision problems, you can add a file existence check once the filename is generated. Here is a source code that can do this for you:

If(file_exists($path.$filename))
{
	// here you can return an error or perform a new generation of the file name
}

A collision problem could happen in a file uploader if:

1) The same file names are uploaded by different users or by the same user even if the content is different.

2) Different files with different names have been uploaded but the hash algorithm produce the same file name.

Limit the number of files

One of the attacks that a hacker can perform against your web application and that can lead to a denial of service is uploading multiple small files. Therefore, you need to limit the number of files that a user can upload. For example, if you allow profile photos uploading, you need to remove the old one from the server once the new one is correctly uploaded.

Advanced security checks and controls

This part of the blog post describes some of the advanced security checks you can add to your application source code or your server configuration. Some of the following recommendations may require server control to be implemented.

  • Remove execute permission from the upload folder
  • Remove write permission on the other files and folders
  • Try to use POST method instead of PUT (or GET!)
  • The compressed file should be checked one by one as a new file.
  • Avoid giving the upload files functionality to unauthenticated and unauthorized peoples
  • Uploaded files should only be accessible to authorized people.
  • Perform a malware scan

The main idea of uploading a malicious file to the server file system is to perform remote code execution. Therefore, removing the execution permission from folders where files are uploaded will enhance the security of your application against file upload.

The PUT HTTP Verb is one of the most dangerous attacks that can happen to a web application. This verb can be exploited to upload files to specific folders without using the application functionalities. Therefore, using this technique the attacker can bypass all the previous security checks and controls.

In most cases, the uploaded files are copied to a public folder, to be easier for the developer to call it whenever it is needed in his source code. However, this technique puts the user’s data in danger especially if those files contain personal information. Therefore, it is very recommended to put all the files in a private folder in the webserver that cannot be directly accessible and then create a function to call their content whenever you need.

It is understandable that developing this functionality will take more programming time, and I fully understand it, but it will really make a big difference while having a cyber-attack.

The last security check that I always recommend especially for big companies, is to perform a malware scan for each uploaded file. In many cases, even the allowed file type can contain malicious codes that can infect your machine. For example, a .pdf CV can contain a malicious file that once your employee clicks on it will trigger malware that encrypts all your system.

Written by: Z. Oualid

Rate it

About the author
Avatar

Z. Oualid

I am a Cyber Security Expert, I have worked with many companies around the globe to secure their applications and their networks. I am certified OSCP and OSCE which are the most recognized and hard technical certifications in the industry of cybersecurity. I am also a Certifed Ethical hacker (CEH). I hope you enjoy my articles :).


Previous post

Post comments (0)

Leave a reply

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