如何在没有 MIME 类型的情况下检查上传的文件是否为图像?

28

我想检查上传的文件是否为图像文件(例如png、jpg、jpeg、gif、bmp),或者是其他类型的文件。问题在于我正在使用Uploadify上传文件,这会更改MIME类型,并给出“text/octal”或某些东西作为MIME类型,无论您上传哪种文件。

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

7个回答

42

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

不仅仅是因为它们可能包含恶意代码,更重要的是因为元标签。我知道有一些网络爬虫会浏览网页,使用隐藏的元标签来查找一些受保护的图片,然后侵犯它们的版权。也许有点偏执,但由于用户上传的图片在版权问题上无法控制,我认为这是一个严肃的问题。

为了解决这些问题,我系统地使用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格式。”这是确保安全的正确方式。 - PauloASilva
事实上,我的一位同事指出,如果允许动画GIF,则此方法无效。没错。 - Alain Tiemblo
我同意你的看法,但是...每个人都知道GIF可以被利用(例如:http://www.phpclasses.org/blog/post/67-PHP-security-exploit-with-GIF-images.html)。如果你需要支持GIF,也许你需要花费一些时间和精力来过滤/清理格式。 - PauloASilva
4
在处理任意数据时直接使用imagecreatefromstring()存在风险,因为攻击者可能通过少量请求使服务器内存被填满而导致DoS攻击。与其从任意数据字符串创建整个图像资源(每像素4个字节,一个RGBa位图),不如首先使用已经在接受答案中提到的getimagesize()来确保图像格式可读并且尺寸合理。 - DaveRandom

33

您可以使用getimagesize(),对于非图像文件,它将返回大小为零的值。


2
就我所知,这实际上是获得此任务完成的公认技巧。如果有更好的方法,我会很感兴趣听听。 - Wesley Murch
文档中说它返回一个包含7个元素的数组,我需要检查哪个元素来确定它是否是图像? - Ali
宽度和高度用零和一表示。 - Scott C Wilson
似乎如果它不是图像,它只会返回false。虽然我接受这个结果,但我仍然想知道是否有其他选项。 - Ali
2
exif_imagetype() 显然更快。 - rybo111
显示剩余2条评论

5
如果Uploadify真的改变了MIME类型,我会认为这是一个错误。这根本没有意义,因为它阻止开发人员使用基于MIME类型的PHP函数: 这是一个小助手函数,它根据文件的前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
您可以通过检查文件开头的魔数来验证图像类型。
例如:每个JPEG文件都以一个“FF D8 FF E0”块开头。
这里有更多关于魔数的信息。

6
不太安全,攻击者可以在前几个字节插入"FF D8 FF E0",再添加一些潜在可执行的 PHP 代码。 - Reacen
2
Reacen,没有什么是安全的,利用JPG元数据部分,PHP代码同样可以被注入到有效的JPG文件中。 - rcode

3
你可以检查文件的前几个字节,通过魔数来确定图像格式。

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

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接