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 $_FILES array. It isn’t uncommon to see upload scripts that attempt to validate the file by checking this field.

In this post, I will tell you why this field cannot be trusted. I 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 to.

Even PHP files…

Spoofing MIME types with cURL and PHP.

In a previous post, I wrote a guide on how to upload files with cURL and PHP. Today, I am 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, I am attempting to upload a PHP file with a fake mime type.

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

A var_dump of the $_FILES array, showing my 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 as “image/jpeg”. Fortunately, the PHP upload form script that I wrote actually checks the extension of the file. As a result, it did not get through.

However, when I modified the validation rules on that upload script to only check the mime type, the file got through. And I was able to browse to the uploaded file and execute it:

As you can see, the PHP file was moved to the uploads directory and I was able to run it.

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

Always validate the file extension.