5

I want to validate my upload files is it an images or not. after searching i found two way that i think is a good way to do it. the first code is:

$whitelist_type = array('image/jpeg', 'image/png','image/gif');
$fileinfo = finfo_open(FILEINFO_MIME_TYPE);

if (!in_array(finfo_file($fileinfo, $file['tmp_name']), $whitelist_type)) {
$error[]  = "Uploaded file is not a valid image";
}

and the second code:

if (!getimagesize($_FILES['photo']['tmp_name'])) {
$error[]  = "Uploaded file is not a valid image";
}

which code is more reliable to check that it's an images and why? or is it any better way than this? thanks.

Eko
  • 443
  • 3
  • 15

4 Answers4

6

finfo_* library would be good but it will work with >= 5.3.0 versions,

AND getimagesize() GD library function that is return image info WxH and size

if image invalid then getimagesize() show warning so better to use to validate image using finfo_* function,

you can also do for cross version code, see below sample code

<?php 
$file = $_FILES['photo'];
$whitelist_type = array('image/jpeg', 'image/png','image/gif');
$error = null;
if(function_exists('finfo_open')){    //(PHP >= 5.3.0, PECL fileinfo >= 0.1.0)
   $fileinfo = finfo_open(FILEINFO_MIME_TYPE);

    if (!in_array(finfo_file($fileinfo, $file['tmp_name']), $whitelist_type)) {
      $error[]  = "Uploaded file is not a valid image";
    }
}else if(function_exists('mime_content_type')){  //supported (PHP 4 >= 4.3.0, PHP 5)
    if (!in_array(mime_content_type($file['tmp_name']), $whitelist_type)) {
      $error[]  = "Uploaded file is not a valid image";
    }
}else{
   if (!@getimagesize($file['tmp_name'])) {  //@ - for hide warning when image not valid
      $error[]  = "Uploaded file is not a valid image";
   }
}
Girish
  • 11,907
  • 3
  • 34
  • 51
4

Why not use exif_imagetype:

if (exif_imagetype($file['tmp_name']) != (IMAGETYPE_JPEG || IMAGETYPE_GIF || IMAGETYPE_PNG)) {
    $error[]  = "Uploaded file is not a valid image";
}

It's probably going to be faster than any of the others. (PHP 4 >= 4.3.0, PHP 5)

l'L'l
  • 44,951
  • 10
  • 95
  • 146
  • is it more reliable? i prefer to choose security rather than performance since I'm gonna use it on small project. – Eko Dec 23 '14 at 07:28
  • I think it's going to prove the most reliable, since it's using PHP's built-in functions; It also specifically reads the files signature to verify the type. That's not to say that a files mime type can't be spoofed, however, there isn't any function out there that can completely prevent that. If you handle your images correctly though the threat of someone being able to use a fake image as an attack vector shouldn't be a major cause for concern. – l'L'l Dec 23 '14 at 07:35
  • maybe i should use this option too beside my other validation just in case. i read some article that hacker can still make a valid filetype and including some code inside, I'm curious about @Archimedix answer that say we should converting images using imagecreatefrom...() i wonder is it can overwrite the code inside the image file. – Eko Dec 23 '14 at 07:45
  • Adding code inside an image (valid or not) can't really be prevented if you're allowing users to upload their own files, regardless of the method you're using to ensure they are really images (not *even `imagecreatefrom()`*). The key thing is for permissions to be set correctly for the files/directories they are uploaded to, and that the PHP handlers are properly set. Using images maliciously has been around for a long time, and there are hundreds are ways they've been used. – l'L'l Dec 23 '14 at 08:20
  • **[This question](http://stackoverflow.com/questions/3115559/exploitable-php-functions)** has some useful info on some common exploits that you might want to read through also. – l'L'l Dec 23 '14 at 08:20
  • but if we want to display the file that user uploaded isn't that file permissions is useless? – Eko Dec 23 '14 at 08:31
  • It's not useless at all — you only want files executable that should be, and restrict permissions on others accordingly with **[chmod](http://php.net/manual/en/function.chmod.php)**. – l'L'l Dec 23 '14 at 08:36
  • In addition, here are a couple more useful articles for you: http://php.net/manual/en/features.file-upload.php, http://php.net/manual/en/function.move-uploaded-file.php – l'L'l Dec 23 '14 at 08:43
  • i know about move upload file. now i have a question about moving file. maybe u can help me answer the question here : http://stackoverflow.com/questions/27616475/renaming-an-upload-file-and-display-it – Eko Dec 23 '14 at 08:50
2

From a security standpoint, you might be better off converting an uploaded file presumed to be an image, see if it succeeds, and keep and serve the converted result from there on.

You could use one of those imagecreatefrom...() functions from the GD library, based on the MIME type you detected, e.g. from the $_FILES array, and/or from exif_imagetype(), finfo_file() etc.

The issue is that there are some exploits out there that pretend to be valid images (and in some cases are valid images) but are also valid JavaScript, Flash or other code containers that may be run by the client's browser under certain circumstances.

See also e.g. https://www.defcon.org/images/defcon-15/dc15-presentations/dc-15-schrenk.pdf

Arc
  • 11,143
  • 4
  • 52
  • 75
  • so is it imagecreatefrom can prevent a php or other script that seem to be valid images and pass above validation? – Eko Dec 23 '14 at 07:31
  • 3
    What I meant is you should recreate the image and save the recreated image instead of the uploaded file because it can remove malicious data in the upload. Recreating may also remove EXIF data such as geolocation what users often do not want to be publicly available to other people (it tells everyone where a photo was taken. If that was at home, people know where you live). – Arc Dec 23 '14 at 10:31
0

The fastest way I use is custom PHP function which reads specific bytes from file. It works much faster than getimagesize when check file is very large (movies, iso images etc.).

fastImageGet('image.jpg');         // returns size and image type in array or false if not image
fastImageGet('image.jpg', 'type'); // returns image type only
fastImageGet('image.jpg', 'size'); // returns image size only

function fastImageGet($file, $what=null) {

    if (!in_array($what, ['size', 'type']))
        $what = null;

    // INIT

    $pos = 0; $str = null;

    if (is_resource($file))
        $fp = $file;

    elseif (!@filesize($file))
        return false;

    else
        try {
            $fp = fopen($file, 'r', false);
        } catch (\Exception $e) {
            return false;
        }


    // HELPER FUNCTIONS

    $getChars = function($n) use (&$fp, &$pos, &$str) {
        $response = null;

        if (($pos + $n - 1) >= strlen($str)) {
            $end = $pos + $n;

            while ((strlen($str) < $end) && ($response !== false)) {
                $need = $end - ftell($fp);

                if (false !== ($response = fread($fp, $need)))
                    $str .= $response;
                else
                    return false;
            }
        }

        $result = substr($str, $pos, $n);
        $pos += $n;
        return $result;
    };

    $getByte = function() use ($getChars) {
        $c = $getChars(1);
        $b = unpack('C', $c);
        return reset($b);
    };

    $readInt = function ($str) {
        $size = unpack('C*', $str);
        return ($size[1] << 8) + $size[2];
    };


    // GET TYPE

    $t2 = $getChars(2);

    if ($t2 === 'BM')
        $type = 'bmp';
    elseif ($t2 === 'GI')
        $type = 'gif';
    elseif ($t2 === chr(0xFF) . chr(0xd8))
        $type = 'jpeg';
    elseif ($t2 === chr(0x89) . 'P')
        $type = 'png';
    else
        $type = false;

    if (($type === false) || ($what === 'type')) {
        fclose($fp);
        return $type;
    }


    // GET SIZE

    $pos = 0;

    if ($type === 'bmp') {
        $chars = $getChars(29);
        $chars = substr($chars, 14, 14);
        $ctype = unpack('C', $chars);
        $size = (reset($ctype) == 40)
            ? unpack('L*', substr($chars, 4))
            : unpack('L*', substr($chars, 4, 8));

    } elseif ($type === 'gif') {
        $chars = $getChars(11);
        $size = unpack('S*', substr($chars, 6, 4));

    } elseif ($type === 'jpeg') {
        $state = null;

        while (true) {

            switch ($state) {

                default:
                    $getChars(2);
                    $state = 'started';
                    break;

                case 'started':
                    $b = $getByte();
                    if ($b === false) {
                        $size = false;
                        break 2;
                    }
                    $state = $b == 0xFF ? 'sof' : 'started';
                    break;

                case 'sof':
                    $b = $getByte();

                    if (in_array($b, range(0xE0, 0xEF)))
                        $state = 'skipframe';

                    elseif (in_array($b, array_merge(range(0xC0, 0xC3), range(0xC5, 0xC7), range(0xC9, 0xCB), range(0xCD, 0xCF))))
                        $state = 'readsize';

                    elseif ($b == 0xFF)
                        $state = 'sof';

                    else
                        $state = 'skipframe';

                    break;

                case 'skipframe':
                    $skip = $readInt($getChars(2)) - 2;
                    $state = 'doskip';
                    break;

                case 'doskip':
                    $getChars($skip);
                    $state = 'started';
                    break;

                case 'readsize':
                    $c = $getChars(7);
                    $size = [$readInt(substr($c, 5, 2)), $readInt(substr($c, 3, 2))];
                    break 2;
            }
        }

    } elseif ($type === 'png') {
        $chars = $getChars(25);
        $size = unpack('N*', substr($chars, 16, 8));
    }


    // COMPLETE

    fclose($fp);

    if (is_array($size))
        $size = array_values($size);

    return ($what === 'size') ? $size : [$type, $size];
}
Pavel Tzonkov
  • 264
  • 3
  • 9