如何在PHP中检查上传文件的文件类型?

50
在PHP网站上,他们建议的唯一真正检查是使用is_uploaded_file()move_uploaded_file(),在这里当然,您通常不希望用户上传任何类型的文件,出于各种原因。
因此,我经常使用一些“严格”的mime类型检查。当然,这非常有缺陷,因为mime类型经常是错误的,用户无法上传其文件。它也很容易被伪造和/或更改。除此之外,每个浏览器和操作系统都以不同的方式处理它们。
另一种方法是检查扩展名,当然比mime类型更容易更改。
如果您只想要图像,可以使用类似于getimagesize()的东西就可以了。
其他类型的文件怎么办?PDF、Word文档或Excel文件?甚至是纯文本文件?
编辑:如果您没有mime_content_typeFileinfo并且system("file -bi $uploadedfile")给出了错误的文件类型,还有哪些选项?

getimagesize()函数明确指出,您不应使用此函数来验证图像是否为图像。不要使用getimagesize()来检查给定文件是否为有效图像。而是使用专门的解决方案,如Fileinfo扩展。http://php.net/manual/en/function.getimagesize.php - Hugo Zonderland
7个回答

34

看一下mime_content_type或者Fileinfo,它们是PHP内置的命令,可以通过查看文件内容来确定文件类型。此外,在上述两个页面的评论中也有其他很好的建议。

个人经验来看,我成功地使用了类似于system("file -bi $uploadedfile")的方法,但我不确定这是否是最佳方法。


23
system("file -bi -- ".escapeshellarg($uploadedfile)) 是更安全的做法。 - Kornel
是的,我在其中进行了一些转义,PHP中总是要小心注入攻击,但我太懒了,没有回去实际检查我使用的命令是什么。 - davr
2
我进行了负评,因为您实际上没有提供演示代码片段,并且引用的代码在其自己的文档页面上也没有演示片段。请更新您的答案和回复,如果有效,我将很乐意点赞。 - John

15

在我看来,所有的MIME类型检查方法都是无用的。

假设你有一个应该具有MIME类型application/pdf的文件。标准的方法是尝试找到类似PDF头部(%PDF-或类似的内容),如果成功,它们会返回“好的,看起来这是一个PDF文件”。但实际上这并没有任何意义。你可以上传一个只包含%PDF-1.4的文件,它也会通过MIME检查。

我的意思是,如果文件具有预期的MIME类型,它将始终通过MIME类型检查,否则结果未定义。


任何认为这个答案不正确的人,请阅读此文。这是一个大开眼界的文章。 - Bhavik Shah
MIME检查并非完全无用。在用户上传未损坏的文件的情况下,它仍然很有用。 - Ari

2

我假设您将拥有一个固定的文件类型白名单,您将接受这些文件类型。

对于每种类型,您需要使用不同的技术来验证它们是否是该格式的有效示例。

有两个相关的问题:

  • 它看起来是否大致像正确的类型?(对于JPEG,您可以检查标题,就像您提到的那样。对于许多基于Unix的格式,您可以检查“魔术Cookie”)

  • 它是否是该类型的有效示例(例如,对于任何类似XML的格式,您可以根据DTD进行验证。)

我认为,对于每种格式,您应该针对每种格式提出单独的问题,因为PDF与ZIP文件相比的答案会有很大不同。


1
如问题所述,getimagesize() 函数对于图像的处理非常完美。 - Darryl Hein

2

我使用与PHP 5.2兼容的mime_content_type,因为我既不能使用需要PHP 5.3的Fileinfo,也不能使用被我的提供商禁用的system()。例如,我可以检查文件是否为文本文件:

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

您可以在我的“PHP目录和子目录监听器&文件查看器和下载器”中查看完整的示例: http://www.galgani.it/software_repository/index.php

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...";
    }
}

2
你不应该仅通过文件扩展名来验证文件类型!这样做非常不安全,你应该获取临时文件的MIME/类型。而且,使用客户端发送的MIME类型也是不安全的。 - Northys
如果我编写了一个病毒,并将其重命名为jpg格式,那么你就完蛋了。 - Deviance
@Deviance,你仍然需要找到一种执行该文件的方法,不是吗? - Jacob Sánchez
@Jacob Sanchez,只需要知道有效载荷URI地址以执行它。通常对于图像,它意味着在网站上显示。攻击者会找到路径的。 - Deviance
但是如果文件扩展名是jpg,你如何让服务器执行该文件呢? - Jacob Sánchez
一个很好的关于这个工作原理的文章是 - https://medium.com/@chamo.wijetunga/hide-payloads-behind-images-and-hacking-windows-fb82cf2f0e7c因此,请对您的图像进行清理。 - Deviance

1
这是来自 iZend 的函数 file_mime_type
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, '; '));
}

0

对于PHP>=5.3.0,您可以使用php的finfo_file(finfo_file)函数获取文件的信息。

对于PHP<5.3.0,您可以使用系统的file命令来获取文件信息。

因此,只需将其合并为一个函数即可。

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


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