在PHP中的上传表单中检查文件扩展名

66

我检查上传的文件是否具有文件扩展名。我的示例方法有效,但现在我需要知道使用 pathinfo 方法是否正确。是否还有其他更好且更快的方法?

$filename = $_FILES['video_file']['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if ($ext !== 'gif' || $ext !== 'png' || $ext !== 'jpg') {
    echo 'error';
}

11
为什么这个字段被称为“video_file”,但你只允许上传图片? - cream
4
我在寻找有效的文件扩展名时发现了这个问题。我不会在这里回答问题,但我想指出您的if语句中存在逻辑错误。条件之间必须使用&&而不是||... 只是想分享我的观点。 - itsols
我建议使用 Reza S 的解决方案。如果涉及到图片,我还建议使用 getimagesize PHP 函数来检测文件是否真的是一张图片。如果未检测到图片,则返回 false。 - Hike Nalbandyan
10个回答

168

使用if( $ext !== 'gif')可能不够高效。如果你允许20种不同的扩展名呢?

尝试:

$allowed = array('gif', 'png', 'jpg');
$filename = $_FILES['video_file']['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
if (!in_array($ext, $allowed)) {
    echo 'error';
}

我正在上传一个带有大写字母的文件名(DOG.JPG),所以我不得不添加 $filename = strtolower($filename); - Mikeys4u
@Baba您好,我们如何在多文件上传中实现这个功能呢? 同时我在$ext上遇到了错误,这意味着我无法从这段代码中获取我的文件扩展名。我该怎么办? - always-a-learner
1
关于“efficient”:你是指“足够”吗? - Peter Mortensen
2
这个程序不安全。用户可以将一个危险的文件重命名为jpg并保存到服务器上。为了确保只有可以按预期处理的图片被上传,我建议检查MimeType。(请参见下面的其他解决方案:https://dev59.com/yGkv5IYBdhLWcg3wpCTJ#10456149) - Sarah Trees

70

检查文件扩展名并不被视为最佳实践。完成此任务的首选方法是检查文件的MIME类型

来自PHP:

<?php
    $finfo = finfo_open(FILEINFO_MIME_TYPE); // Return MIME type
    foreach (glob("*") as $filename) {
        echo finfo_file($finfo, $filename) . "\n";
    }
    finfo_close($finfo);
?>

上面的示例将输出类似于您应该进行检查的内容。
text/html
image/gif
application/vnd.ms-excel

虽然MIME类型也可以被欺骗(编辑文件的前几个字节并修改魔数),但这比编辑文件名要困难。因此,您永远无法百分之百确定该文件类型实际上是什么,处理用户上传/发送的文件时应格外小心。


4
我几乎怀疑是否有一种方法可以获得真正的文件扩展名。到处都是“分析文件名”的答案,这是最糟糕的方法...非常感谢您提供的答案! - instead
2
这个答案应该放在最上面,因为更改文件扩展名对于某些人来说非常容易! - Talk nerdy to me
如果 MIME 是假的,你有什么建议?恶意用户可以使用脚本或其他自动化应用程序上传文件,这些应用程序允许发送 HTTP POST 请求,从而使他能够发送虚假的 MIME 类型。 - Prafulla Kumar Sahu
3
你可以在这里找到关于你问题的详细答案:https://dev59.com/7Wsz5IYBdhLWcg3wQFUq#8028436 - Reza S
1
我找到了一个更好的资源,也许这个链接将来会被删除,但现在它似乎是一个很棒的资源。https://www.acunetix.com/websitesecurity/upload-forms-threat/ - Prafulla Kumar Sahu
2
我认为BBKing也在寻找一种防止上传Webshell的方法。MIME类型可以很容易地在HTTP请求中更改。检查文件扩展名是一种更好(虽然不理想)的方法,因为没有正确扩展名的文件将无法执行。请查看OWASP资源:• https://www.owasp.org/index.php/Unrestricted_File_Upload - Silver

14

就我个人而言,我更喜欢使用preg_match()函数:

if(preg_match("/\.(gif|png|jpg)$/", $filename))

或者 in_array()
$exts = array('gif', 'png', 'jpg'); 
if(in_array(end(explode('.', $filename)), $exts)

使用in_array()函数可以很方便地验证许多扩展并解决性能问题。另一种验证图像文件的方法是使用@imagecreatefrom*()函数,如果该函数失败,则表示该图像无效。
例如:
function testimage($path)
{
   if(!preg_match("/\.(png|jpg|gif)$/",$path,$ext)) return 0;
   $ret = null;
   switch($ext)
   {
       case 'png': $ret = @imagecreatefrompng($path); break;
       case 'jpeg': $ret = @imagecreatefromjpeg($path); break;
       // ...
       default: $ret = 0;
   }

   return $ret;
}

然后:
$valid = testimage('foo.png');

假设foo.png是一个带有.png扩展名的PHP脚本文件,上述函数会失败。它可以避免像shell更新和LFI这样的攻击。

6

pathinfo很酷,但您的代码可以改进:

$filename = $_FILES['video_file']['name'];
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$allowed = array('jpg','png','gif');
if( ! in_array( $ext, $allowed ) ) {echo 'error';}

当然,仅检查文件名的扩展名不能保证文件类型是有效的图像。您可以考虑使用getimagesize函数来验证上传的图像文件。


4
文件类型也可以通过其他方式进行检查。我认为这是检查上传的文件类型最简单的方法。如果你正在处理一张图片文件,则使用以下代码。如果你要处理视频文件,则在if块中用视频检查替换图像检查。祝你玩得开心。
$img_up = $ _FILES ['video_file'] ['type'];
$img_up_type = explode("/", $img_up);
$img_up_type_firstpart = $img_up_type [0];
如果($img_up_type_firstpart == "image") { // image是图像文件类型,如果需要检查视频文件类型,则可以处理视频。

/* 执行您的逻辑代码 */

}

$_FILES['field_name']['type'] 是用户定义的,因此不应该依赖它进行验证。您应该使用 PHP 的 exif_imagetype 或 getimagesize 函数来检查图像的详细信息/类型。 - dading84

3
为了正确实现这一点,您最好通过检查MIME类型来完成。
function get_mime($file) {
  if (function_exists("finfo_file")) {
    $finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
    $mime = finfo_file($finfo, $file);
    finfo_close($finfo);
    return $mime;
  } else if (function_exists("mime_content_type")) {
    return mime_content_type($file);
  } else if (!stristr(ini_get("disable_functions"), "shell_exec")) {
    // https://dev59.com/ZHVC5IYBdhLWcg3w9GLM#134930
    $file = escapeshellarg($file);
    $mime = shell_exec("file -bi " . $file);
    return $mime;
  } else {
    return false;
  }
}
//pass the file name as
echo(get_mime($_FILES['file_name']['tmp_name']));

2
不确定这种方法是否会有更快的计算时间,但另一种选择是...
$acceptedFormats = array('gif', 'png', 'jpg');

if(!in_array(pathinfo($filename, PATHINFO_EXTENSION), $acceptedFormats))) {
    echo 'error';
}

1
我认为这可能适合你。
//<?php
    //checks file extension for images only
    $allowed =  array('gif','png' ,'jpg');
    $file = $_FILES['file']['name'];
    $ext = pathinfo($file, PATHINFO_EXTENSION);
        if(!in_array($ext,$allowed) ) 
            { 
//?>
<script>
       alert('file extension not allowed');
       window.location.href='some_link.php?file_type_not_allowed_error';
</script>

//<?php
exit(0);
    }
//?>

0
如何在上传之前在表单上验证文件扩展名(只限jpg / jpeg)。 这是在此处发布的另一篇答案的变体,其中包括一个数据过滤器,当评估其他已发布的表单值时可能会有用。 注意:如果为空,则将错误留空,因为这是我的用户的选项,不是要求。
<?php
if(isset($_POST['save'])) {

//Validate image
if (empty($_POST["image"])) {
$imageError = "";
} else {
$image = test_input($_POST["image"]);
$allowed =  array('jpeg','jpg');
$ext = pathinfo($image, PATHINFO_EXTENSION);
if(!in_array($ext,$allowed) ) {
$imageError = "jpeg only";
}
}

}

// Validate data
function test_input($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
<?

你的 HTML 代码将会长成这个样子:

<html>
<head>
</head>

<body>
<!-- Validate -->
<?php include_once ('validate.php'); ?>

<!-- Form -->
<form method="post" action="">

<!-- Image -->
<p>Image <?php echo $imageError; ?></p>
<input type="file" name="image" value="<?php echo $image; ?>" />

<p><input type="submit" name="save" value="SAVE" /></p>
</form>

</body>
</html>

-2
请使用这个函数。
 function check_image_extension($image){

        $images_extentions = array("jpg","JPG","jpeg","JPEG","png","PNG");

        $image_parts = explode(".",$image);

        $image_end_part = end($image_parts);

        if(in_array($image_end_part,$images_extentions ) == true){

            return time() . "." . $image_end_part;


        }else{

            return false;
        }


    }

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