【问题标题】:How to check if an uploaded file is an image without mime type?如何检查上传的文件是否是没有 mime 类型的图像?
【发布时间】:2011-09-22 23:32:54
【问题描述】:

我想检查上传的文件是图像文件(例如 png、jpg、jpeg、gif、bmp)还是其他文件。问题是我正在使用 Uploadify 上传文件,这会更改 mime 类型并提供“文本/八进制”或其他内容作为 mime 类型,无论您上传哪种文件类型。

除了使用PHP检查文件扩展名之外,还有其他方法可以检查上传的文件是否为图像吗?

【问题讨论】:

    标签: php image-processing file-upload upload mime-types


    【解决方案1】:

    我对这个主题的想法很简单:所有上传的图片都是邪恶的。

    不仅因为它们可能包含恶意代码,还因为元标记。我知道爬虫浏览网络以使用隐藏的元标记查找一些受保护的图像,然后使用他们的版权。也许有点偏执,但由于用户上传的图片无法控制版权问题,我会认真考虑。

    为了摆脱这些问题,我使用 gd 系统地将所有上传的图像转换为 png。这有很多优点:图像从最终的恶意代码和元标记中清除,我对所有上传的图像只有一种格式,我可以调整图像大小以符合我的标准,并且...... 我立即知道图片是否有效!如果图片无法打开转换(使用imagecreatefromstring不关心图片格式),则认为图片无效。

    一个简单的实现可能如下所示:

    function imageUploaded($source, $target)
    {
       // check for image size (see @DaveRandom's comment)
       $size = getimagesize($source);
       if ($size === false) {
          throw new Exception("{$source}: Invalid image.");
       }
       if ($size[0] > 2000 || $size[1] > 2000) {
          throw new Exception("{$source}: Too large.");
       }
    
       // loads it and convert it to png
       $sourceImg = @imagecreatefromstring(@file_get_contents($source));
       if ($sourceImg === false) {
          throw new Exception("{$source}: Invalid image.");
       }
       $width = imagesx($sourceImg);
       $height = imagesy($sourceImg);
       $targetImg = imagecreatetruecolor($width, $height);
       imagecopy($targetImg, $sourceImg, 0, 0, 0, 0, $width, $height);
       imagedestroy($sourceImg);
       imagepng($targetImg, $target);
       imagedestroy($targetImg);
    }
    

    测试它:

    header('Content-type: image/png');
    imageUploaded('http://www.dogsdata.com/wp-content/uploads/2012/03/Companion-Yellow-dog.jpg', 'php://output');
    

    这并不能完全回答你的问题,因为这与接受的答案是同一种黑客,但我给你我使用它的理由,至少 :-)

    【讨论】:

    • 我完全同意@Alain Tiemblo “我使用 gd 系统地将所有上传的图像转换为 png”。这是保障安全的方法。
    • 确实,我的一位同事指出,如果您允许使用动画 gif,这将不起作用。是的。
    • 我同意你的观点,但是......每个人都知道 GIF 可以被利用 (example)。如果您需要支持 GIF,您可能需要投入一些时间和精力来过滤/清理格式。
    • 对任意数据直接使用imagecreatefromstring() 也是危险的,因为DoS 式攻击仍然有可能只用几个请求来填充服务器的内存。而不是从任意数据字符串中创建整个图像资源(每像素 4 个字节,RGBa 位图),您应该首先使用getimagesize(),如接受的答案中所述,以确保图像是可读格式并且尺寸合理。
    【解决方案2】:

    您可以使用getimagesize(),它为非图像的大小返回零。

    【讨论】:

    • 据我所知,这实际上是完成此任务的公认技巧。如果有更好的方法,我很想听听。
    • 文档说它返回一个包含 7 个元素的数组,我需要检查哪个元素来查看它是否是图像?
    • 宽度和高度为零和一。
    • 如果它不是图像,似乎它只会返回 false。我接受了这个,但我仍然想知道是否还有其他选择。
    • exif_imagetype() 显然要快得多。
    【解决方案3】:

    您可以通过检查文件开头的幻数来验证图像类型。

    例如:每个 JPEG 文件都以 "FF D8 FF E0" 块开头。

    这里有更多关于magic numbers的信息

    【讨论】:

    • 不安全imo,攻击者可以“FF D8 FF E0”第一个字节,然后添加一些可能执行的php代码
    • Reacen,没有什么是安全的,PHP 代码也可以通过使用 JPG 元数据部分注入到有效的 JPG 文件中。
    【解决方案4】:

    如果 Uploadify 真的改变了 mime 类型 - 我会认为这是一个错误。 这根本没有意义,因为这会阻止开发人员使用 PHP 中基于 mime 类型的函数:

    这是一个小辅助函数,它根据文件的前 6 个字节返回 mime 类型。

    /**
     * Returns the image mime-type based on the first 6 bytes of a file
     * It defaults to "application/octet-stream".
     * It returns false, if problem with file or empty file.
     *
     * @param string $file 
     * @return string Mime-Type
     */
    function isImage($file)
    {
        $fh = fopen($file,'rb');
        if ($fh) { 
            $bytes = fread($fh, 6); // read 6 bytes
            fclose($fh);            // close file
    
            if ($bytes === false) { // bytes there?
                return false;
            }
    
            // ok, bytes there, lets compare....
    
            if (substr($bytes,0,3) == "\xff\xd8\xff") { 
                return 'image/jpeg';
            }
            if ($bytes == "\x89PNG\x0d\x0a") { 
                return 'image/png';
            }
            if ($bytes == "GIF87a" or $bytes == "GIF89a") { 
                return 'image/gif';
            }
    
            return 'application/octet-stream';
        }
        return false;
    }
    

    【讨论】:

      【解决方案5】:

      您可以检查文件的前几个字节以查找magic number 以确定图像格式。

      【讨论】:

        【解决方案6】:

        尝试使用exif_imagetype 检索图像的实际类型。如果文件太小会抛出错误,如果找不到它会返回 false

        【讨论】:

          【解决方案7】:

          不能用finfo_file查询文件吗?

          $finfo = finfo_open(FILEINFO_MIME_TYPE);
          $mimetype = finfo_file($finfo, $filename); //should contain mime-type
          finfo_close($finfo);
          

          此答案未经测试,但基于 Uploadify 论坛上的 this forum discussion

          我还要指出 finfo 应该 "try to guess the content type and encoding of a file by looking for certain magic byte sequences at specific positions within the file" 所以在我看来这应该仍然有效,即使 Uploadify 指定了错误的 mime 类型。

          【讨论】:

            猜你喜欢
            • 2017-06-11
            • 1970-01-01
            • 1970-01-01
            • 2019-09-04
            • 1970-01-01
            • 2010-10-27
            • 2014-02-20
            相关资源
            最近更新 更多