50

On the PHP website, the only real checking they suggest is using is_uploaded_file() or move_uploaded_file(), here. Of course you usually don't want user's uploading any type of file, for a variety of reasons.

Because of this, I have often used some "strict" mime type checking. Of course this is very flawed because often mime types are wrong and users can't upload their file. It is also very easy to fake and/or change. And along with all of that, each browser and OS deals with them differently.

Another method is to check the extension, which of course is even easier to change than mime type.

If you only want images, using something like getimagesize() will work.

What about other types of files? PDFs, Word documents or Excel files? Or even text only files?

Edit: If you don't have mime_content_type or Fileinfo and system("file -bi $uploadedfile") gives you the wrong file type, what other options are there?

Alix Axel
  • 151,645
  • 95
  • 393
  • 500
Darryl Hein
  • 142,451
  • 95
  • 218
  • 261
  • The `getimagesize()` function clearly states that you should not use this function to validate if an image is an image. `Do not use getimagesize() to check that a given file is a valid image. Use a purpose-built solution such as the Fileinfo extension instead.` http://php.net/manual/en/function.getimagesize.php – Hugo Zonderland Jun 07 '17 at 10:27

7 Answers7

34

Take a look at mime_content_type or Fileinfo. These are built-in PHP commands for determining the type of a file by looking at the contents of the file. Also check the comments on the above two pages, there are some other good suggestions.

Personally I've had good luck using something that's essentially system("file -bi $uploadedfile"), but I'm not sure if that's the best method.

Darryl Hein
  • 142,451
  • 95
  • 218
  • 261
davr
  • 18,877
  • 17
  • 76
  • 99
  • 22
    system("file -bi -- ".escapeshellarg($uploadedfile)) is safer. – Kornel Nov 22 '08 at 23:26
  • Yeah, I do some escaping in there, always gotta be careful of injection attacks in PHP, but I was too lazy to go back and actually check what the command I used was. – davr Nov 24 '08 at 19:28
  • 2
    I down-voted because you did not actually provide a demonstration snippet of code and referenced code that had no demonstration snippets on it's own documentation page. Please update your answer and reply and if it works I'll gladly up-vote. – John May 20 '15 at 12:07
15

IMHO, all MIME-type checking methods are useless.

Say you've got which should have MIME-type application/pdf. Standard methods are trying to find something that looks like a PDF header (%PDF- or smth. like that) and they will return 'Okay, seems like this is a PDF file' on success. But in fact this doesn't means anything. You can upload a file containing only %PDF-1.4 and it will pass MIME-check.

I mean if the file has an expected MIME-type - it will always pass the MIME-type check otherwise the result is undefined.

Czechnology
  • 14,832
  • 10
  • 62
  • 88
Sudden Def
  • 10,031
  • 3
  • 18
  • 8
  • Anybody who thinks this answer is not correct, [read this](http://stackoverflow.com/questions/8444638/how-to-read-header-of-a-file-uploaded-in-php#comment-10454480). An eye-opener. – Bhavik Shah May 21 '16 at 04:54
  • mime check isn't completely useless. it is still useful in the condition where user uploaded un-corrupted file. – Ari Jan 24 '17 at 19:39
2
if(isset($_FILES['uploaded'])) {
    $temp = explode(".", $_FILES["uploaded"]["name"]);

    $allowedExts = array("txt","htm","html","php","css","js","json","xml","swf","flv","pdf","psd","ai","eps","eps","ps","doc","rtf","ppt","odt","ods");

    $extension = end($temp);
    if( in_array($extension, $allowedExts)) {
       //code....

    } else {
        echo "Error,not Documentum type...";
    }
}
Manoj Sharma
  • 1,467
  • 2
  • 13
  • 20
  • 2
    You should not validate file type using extension! This is not safe at all and you should get mime/type of tmp file. Also using mime type sent by client is not safe. – Northys Nov 30 '16 at 00:56
  • If I coded a virus, and rename as jpg. You're dead. – Deviance Dec 26 '16 at 10:31
  • @Deviance you'd still need to find a way to execute that file, wouldn't you? – Jacob Sánchez Jul 06 '20 at 02:40
  • @Jacob Sanchez , Just need to know the payload uri address to execute it. Usually for image it is meant to display somewhere on the site. Attackers will just find the paths regardless. – Deviance Jul 07 '20 at 04:30
  • Yeah but how would you get the server to execute the file if it has a jpg extension? – Jacob Sánchez Jul 07 '20 at 06:29
  • A good article of how this work is - https://medium.com/@chamo.wijetunga/hide-payloads-behind-images-and-hacking-windows-fb82cf2f0e7c Hence, sanitize your image. – Deviance Aug 07 '20 at 08:20
2

I assume you are going to have a fixed white-list of file-types that you will accept.

For each of these types, you are going to have to use different techniques to verify that they are valid examples of that format.

There are two related questions:

  • Does it look roughly like it might be the right type? (For JPEG, you could check the headers, as you mentioned. For many Unix-based formats, you could check the "magic cookie".)

  • Is it actually a valid example of that type (e.g. For any XML-like format, you could validate against a DTD.)

I think that, for each format, you should ask separate questions for each one, because the answer will be quite different for PDFs compared to ZIP files.

Oddthinking
  • 24,359
  • 19
  • 83
  • 121
2

I used mime_content_type that is compatible with PHP 5.2, because I can use neither Fileinfo (it requires PHP 5.3) nor system(), that is disabled by my provider. For example, I check if a file is a text file so:

if (strcmp(substr(mime_content_type($f),0,4),"text")==0) { ... }

You can see a full example in my "PHP Directory and Subdirectory Listener & File Viewer and Downloader" at: http://www.galgani.it/software_repository/index.php

Ben
  • 54,723
  • 49
  • 178
  • 224
Francesco Galgani
  • 6,137
  • 3
  • 20
  • 23
1

Here is the function file_mime_type from iZend:

function file_mime_type($file, $encoding=true) {
    $mime=false;

    if (function_exists('finfo_file')) {
        $finfo = finfo_open(FILEINFO_MIME);
        $mime = finfo_file($finfo, $file);
        finfo_close($finfo);
    }
    else if (substr(PHP_OS, 0, 3) == 'WIN') {
        $mime = mime_content_type($file);
    }
    else {
        $file = escapeshellarg($file);
        $cmd = "file -iL $file";

        exec($cmd, $output, $r);

        if ($r == 0) {
            $mime = substr($output[0], strpos($output[0], ': ')+2);
        }
    }

    if (!$mime) {
        return false;
    }

    if ($encoding) {
        return $mime;
    }

    return substr($mime, 0, strpos($mime, '; '));
}
iZend
  • 11
  • 1
0

For PHP>=5.3.0, you can use php's finfo_file(finfo_file) function to get the file infomation about the file.

For PHP<5.3.0, you can use your's system's file command to get the file information.

So just make it in one function,

var_dump(mime_type("wiki templete.txt"));   // print string(10) "text/plain"

function mime_type($file_path)
{
    if (function_exists('finfo_open')) {
        $finfo = new finfo(FILEINFO_MIME_TYPE, null);
        $mime_type = $finfo->file($file_path);
    }
    if (!$mime_type && function_exists('passthru') && function_exists('escapeshellarg')) {
        ob_start();
        passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($file_path)), $return);
        if ($return > 0) {
            ob_end_clean();
            $mime_type = null;
        }
        $type = trim(ob_get_clean());
        if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
            $mime_type = null;
        }
        $mime_type = $match[1];
    }
    return $mime_type;
}

MimeTypes

LF00
  • 27,015
  • 29
  • 156
  • 295