Spoofing MIME types: Why you can’t trust the type field in $_FILES.

A lot of PHP developers make the mistake of trusting the type field in the $_FILES array. It isn’t uncommon to see upload scripts that attempt to validate the file by checking this field.

In this post, we will tell you why this field cannot be trusted.

We will also show you a working example of how you can use PHP and cURL to easily spoof the mime type of a file.

What is the “type” field in $_FILES?

The “type” field in the $_FILES array represents the mime type of the file that has been uploaded. A mime type is something that is supposed to describe the file and its structure.

For example, if the file is a JPEG image, then the mime type should be “image/jpeg”. If it’s an Excel file, then the mime type will probably be “application/vnd.ms-excel”.

Think of it as a “hint” about the identity of the file.

Why can’t you trust the “type” field?

The problem with the type field in the $_FILES array is that it comes from the client. This means that the person who is uploading the file can change it to anything they want to.

For example, they could change the mime type of a .exe file to “image/jpeg” or they could change a .php file to “text/csv”.

If that happens, your mime type validation check will let the file pass. As a result, an attacker will be free to upload any kind of file that they want.

That includes PHP files.

Spoofing MIME types with cURL and PHP.

In a previous post, we wrote a guide on how to upload files with cURL and PHP.

Today, we are going to take that code and modify it to spoof the mime type:

//The URL I am uploading to.
$uploadUrl = 'http://localhost/test/upload-script.php';

//Attempting to upload a PHP file.
$badFile = 'C:\wamp64\www\test\file.php';
 
$ch = curl_init($uploadUrl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

//Set the MIME type of the PHP file to image/jpeg
$badFile = new CURLFile($badFile, 'image/jpeg');
 
$postFields = array(
    'user_file' => $badFile,
);
 
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
 
$result = curl_exec($ch);
echo $result;

In the code above, we are attempting to upload a PHP file with a fake mime type.

On line 12, we instantiated the CURLFile object and set the second parameter to “image/jpeg”. As a result, cURL will upload our PHP file with the type “image/jpeg”.

A var_dump of the $_FILES array, showing our upload:

Spoofed Mime Type

A screenshot showing the contents of the $_FILES array.

Despite the fact that our uploaded file is clearly a PHP file, the mime type is showing up as “image/jpeg”.

Fortunately, the PHP upload form script that we wrote actually checks the extension of the file. As a result, it did not get through.

However, when we modified the validation rules on that upload script to only check the mime type, the file got through.

And we were able to browse to the uploaded file and execute it:

As you can see, the PHP file was moved to the uploads directory and we were able to execute it.

If an attacker can do this on your web server, it is extremely bad news.

Always validate the file extension.