PHP:如何正确检查文件的MIME类型?

10

我有一个输入框,可以上传图片,唯一允许的图片类型是:

png, jpg, jpeg

在将图片插入到数据库之前,它会检查图片的格式是否为png、jpg或jpeg。但出于安全原因,现在我需要在第一个检查之前或之后检查MIME类型。

我该如何做?以下是我的代码:

<?php

$iAmountOfFiles = count($_FILES['Filename']['name']);

while($iAmountOfFiles >= 1) {

    $iAmountOfFiles--;

    $aFileProperties = pathinfo($_FILES['Filename']['name'][$iAmountOfFiles]);
    if(!in_array(strtolower($aFileProperties["extension"]), $aExtensionWhitelist)) {
        echo "Bestands type niet toegestaan";
        // exit;
        continue;
    }

    $sTarget = ROOT.BACKEND."/pages/bezienswaardigheden-toevoegen/uploads/";
    $sUniqueFileNameHash = hash('adler32', time().rand());
    $Filename = basename($sUniqueFileNameHash."-".$_FILES['Filename']['name'][$iAmountOfFiles]);
    $Filename = basename($aFileProperties["filename"]."-".$sUniqueFileNameHash.".".strtolower($aFileProperties["extension"]));

    // Writes the Filename to the server
    if(move_uploaded_file($_FILES['Filename']['tmp_name'][$iAmountOfFiles], $sTarget.$Filename)) {

    // here needs to come the mime check

请先阅读[提问指南]。在向我们提问之前,您应该自己付出一些努力。目前,您的问题并没有表明您实际上尝试过,您基本上只是在这里列出了您的要求。一个简单的“我该如何做这个?”并不是在这里提问的适当方式。我们不是为了让您可以将任何研究外包给我们而存在的。 - 04FS
这个回答解决了你的问题吗?在PHP中从文件名获取MIME类型 - Martin
这里是代码:$result = new finfo();print $result->file($filename, FILEINFO_MIME_TYPE); - Martin
3个回答

26

开发者们通常会依赖于$_FILES['input_name']['type']来获取MIME类型。但这是非常容易受到攻击的。因为恶意用户可以将image/jpgimage/pngimage/gif等MIME类型之一设置到实际上不是图片的文件上。在这种情况下,恶意用户可能会让您的脚本上传其他类型的文件而不是图片,并执行他们的目的,这是危险的。

因此,我建议不要依赖以下代码片段来获取文件的MIME类型。

$_FILES['input_name']['type'];
我建议您使用mime_content_type()函数获取MIME类型,但需要借助其他PHP内置函数来完成。其中一个函数是is_uploaded_file()函数。它的作用是:

这有助于确保恶意用户没有尝试欺骗脚本在不应该工作的文件上工作 - 例如 /etc/passwd。

如果上传的文件可能揭示其内容甚至向同一系统上的其他用户公开,则此类检查尤为重要。

因此,为了使此函数正常工作,它需要一个特定的参数。请查看下面的代码:

if (is_uploaded_file($_FILES['input_name']['tmp_name'])) {
    // Do other stuff.
}
该函数成功返回true,否则返回false。因此,如果返回true,则文件正常。感谢该函数的帮助。现在,mime_content_type()函数开始发挥作用了。如何?请看下面的代码:
if (is_uploaded_file($_FILES['input_name']['tmp_name'])) {
    // Notice how to grab MIME type.
    $mime_type = mime_content_type($_FILES['input_name']['tmp_name']);

    // If you want to allow certain files
    $allowed_file_types = ['image/png', 'image/jpeg', 'application/pdf'];
    if (! in_array($mime_type, $allowed_file_types)) {
        // File type is NOT allowed.
    }

    // Set up destination of the file
    $destination = '/path/to/move/your/file/';

    // Now you move/upload your file
    if (move_uploaded_file ($_FILES['input_name']['tmp_name'] , $destination)) {
        // File moved to the destination
    }
}

顺便提一下,对于新手来说,不要尝试使用此函数来获取远程URL的MIME类型。 下面的代码将无法正常工作:

mime_content_type('http://www.example.com/uploads/example.png');

但是下面这个会起作用:

mime_content_type('/source/to/your/file/etc.png');

希望您从现在开始能够享受上传文件的乐趣。


move_uploaded_file()函数还会检查是否为有效的上传文件。从文档中可以看到:"如果filename不是有效的上传文件,则不执行任何操作,并且move_uploaded_file()将返回FALSE。" - Fahmi
@Fahmi 是的,它可以!但在使用 move_uploaded_file() 上传文件之前,最好使用 is_uploaded_file() 进行检查。因为它可以确保没有恶意用户试图欺骗正在执行上传文件的脚本。 - unclexo

2

你可以通过文件内容获取 MIME 类型:

public static function getMimeTypeFromFileContent(string &$content): string
{
    return (new finfo(FILEINFO_MIME_TYPE))->buffer($content);
}

通过$content = file_get_contents($pathToFile);可以获取文件内容。


1
使用上述简单的[你可以说更大的] 函数,您可以提取或获取文件或内容的MIME类型。
但是,在使用此功能之前,您可能需要完成一些预配置,
像您必须确保已在php.ini文件中打开或配置了curl扩展、文件系统相关扩展和finfo扩展。
这里,我将简要描述这个函数的整个过程。
1.首先,我们将所有更新的mime类型存储为一个数组,从官方apache mime类型网址中获取。
您也可以在apache conf目录中获取此mime类型文件,而不是使用url。在此函数中,我们使用实时url获取所有mime类型。
0.但是,这个函数的第零步骤是验证apache url是否正常。
验证url后,如果url被验证[意味着活动的],我们将所有mimes从该url存储为一个名为$mimes的数组。
如果URL不存在或无法访问,我们将手动创建一个包含一些常见扩展名的数组。
然后,我们会验证内容是否为文件。
接下来,我们使用PHP的pathinfo函数检查是否有文件扩展名。如果有,就将其存储下来。
在此之后,我们会用我们的内容扩展名作为$mimes数组的索引,检查$mimes数组。
最后,我们通过$content_mime变量以$mimes数组的索引值作为内容MIME类型返回。
就是这样。
<?php
    /**
     * **get_content_mime_type
     *
     * @param  string $content, the content or the file whose mime type you want to know.
     * @return string
     */
    function get_content_mime_type($content)
    {
        $url = 'http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types';
        $url_live = false;
        $handle = curl_init($url);
        curl_setopt_array($handle, array(
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_NOBODY => true,
            CURLOPT_HEADER => false,
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false
        ));
        $response = curl_exec($handle);
        $httpCode = curl_getinfo($handle, CURLINFO_EFFECTIVE_URL);
        $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
        if ($httpCode == 200)
        {
            $url_live = true;
        }
        $url_live = $url_live;
        curl_close($handle);
        $mimes = array();
        if ($url_live)
        {
            $mimes_file = file_get_contents($url);
            preg_match_all('#^([^\s]{2,}?)\s+(.+?)$#ism', $mimes_file, $matches, PREG_SET_ORDER);
            foreach ($matches as $match)
            {
                $exts = explode(" ", $match[2]);
                foreach ($exts as $ext)
                {
                    $mimes[$ext] = $match[1];
                }
            }
        }
        else
        {
            $mimes = array(
                'txt' => 'text/plain',
                'htm' => 'text/html',
                'html' => 'text/html',
                'php' => 'text/html',
                'css' => 'text/css',
                'js' => 'application/javascript',
                'json' => 'application/json',
                'xml' => 'application/xml',
                'swf' => 'application/x-shockwave-flash',
                'flv' => 'video/x-flv',
                // images
                'png' => 'image/png',
                'jpe' => 'image/jpeg',
                'jpeg' => 'image/jpeg',
                'jpg' => 'image/jpeg',
                'gif' => 'image/gif',
                'bmp' => 'image/bmp',
                'ico' => 'image/vnd.microsoft.icon',
                'tiff' => 'image/tiff',
                'tif' => 'image/tiff',
                'svg' => 'image/svg+xml',
                'svgz' => 'image/svg+xml',
                // archives
                'zip' => 'application/zip',
                'rar' => 'application/x-rar-compressed',
                'exe' => 'application/x-msdownload',
                'msi' => 'application/x-msdownload',
                'cab' => 'application/vnd.ms-cab-compressed',
                // audio/video
                'mp3' => 'audio/mpeg',
                'qt' => 'video/quicktime',
                'mov' => 'video/quicktime',
                // adobe
                'pdf' => 'application/pdf',
                'psd' => 'image/vnd.adobe.photoshop',
                'ai' => 'application/postscript',
                'eps' => 'application/postscript',
                'ps' => 'application/postscript',
                // ms office
                'doc' => 'application/msword',
                'rtf' => 'application/rtf',
                'xls' => 'application/vnd.ms-excel',
                'ppt' => 'application/vnd.ms-powerpoint',
                'docx' => 'application/msword',
                'xlsx' => 'application/vnd.ms-excel',
                'pptx' => 'application/vnd.ms-powerpoint',
                // open office
                'odt' => 'application/vnd.oasis.opendocument.text',
                'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
            );
        }
        $content_mime = 'unknown';
        if (is_file($content))
        {
            if (isset(pathinfo($content) ['extension']))
            {
                $content_ext = pathinfo($content) ['extension'];
                if (isset($mimes[$content_ext]))
                {
                    $content_mime = $mimes[$content_ext];
                }
                else
                {
                    if (is_readable($content) && is_executable($content))
                    {
                        $finfo = finfo_open(FILEINFO_MIME_TYPE);
                        $content_mime = finfo_file($finfo, $content);
                        if ($content_mime === null | $content_mime === "")
                        {
                            $content_mime = "application/octet-stream";
                        }
                        else
                        {
                            $content_mime = $content_mime;
                        }
                        finfo_close($finfo);
                    }
                    else
                    {
                        $content_mime = "application/octet-stream";
                    }
                }
            }
        }
        else
        {
            // return whatever you want
            // $content_mime = 'unknown';
            
        }
        $content_mime = $content_mime;
        return $content_mime;
    }
    ?>

虽然这段代码可能回答了OP的问题,但是您可以通过附加关于代码如何解决问题的说明来使您的答案更好。 - Simas Joneliunas
1
@simas-joneliunas 是的,已添加。 - Asaduzzaman Atik

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