文件上传漏洞

Posted 简简人事

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了文件上传漏洞相关的知识,希望对你有一定的参考价值。

漏洞描述

开发中文件上传功能很常见,作为开发者,在完成功能的基础上我们一般也要做好安全防护。
文件处理一般包含两项功能,用户上传和展示文件,如上传头像。

文件上传攻击示例
upload.php

<?php
$uploaddir = \'uploads/\'; 
$uploadfile = $uploaddir . basename($_FILES[\'userfile\'][\'name\']);
if (move_uploaded_file($_FILES[\'userfile\'][\'tmp_name\'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\\n";
} 
else {
    echo "File uploading failed.\\n";
}
?>

upload.html

<form name="upload" action="upload.php" method="POST" ENCTYPE="multipart/formdata">
Select the file to upload: <input type="file" name="userfile">
<input type="submit" name="upload" value="upload">
</form>

上述代码未经过任何验证,恶意用户可以上传php文件,代码如下

<?php eval($_GET[\'command\']);?>

恶意用户可以通过访问 如http://server/uploads/shell.php?command=phpinfo(); 来执行远程命令

Content-type验证

upload.php

<?php
if($_FILES[\'userfile\'][\'type\'] != "image/gif") {//获取Http请求头信息中ContentType
    echo "Sorry, we only allow uploading GIF images";
    exit;
}
$uploaddir = \'uploads/\';
$uploadfile = $uploaddir.basename($_FILES[\'userfile\'][\'name\']);
if (move_uploaded_file($_FILES[\'userfile\'][\'tmp_name\'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\\n";
} else {
    echo "File uploading failed.\\n";
}
?>


该方式是通过Http请求头信息进行验证,可通过修改Content-type ==> image/jpg绕过验证,可以通过脚本或BurpSuite、fiddle修改
如下
Content-Disposition: form-data; name="userfile"; filename="shell.php"
Content-Type: image/gif

图片类型验证
该方法通过读取文件头中文件类型信息,获取文件类型

备注:如JPEG/JPG文件头标识为FFD8

upload.php

<?php
$imageinfo = getimagesize($_FILES[\'userfile\'][\'tmp_name\']);
if($imageinfo[\'mime\'] != \'image/gif\' && $imageinfo[\'mime\'] != \'image/jpeg\') {
    echo "Sorry, we only accept GIF and JPEG images\\n";
    exit;
}
$uploaddir = \'uploads/\';
$uploadfile = $uploaddir . basename($_FILES[\'userfile\'][\'name\']);
if (move_uploaded_file($_FILES[\'userfile\'][\'tmp_name\'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\\n";
} else {
    echo "File uploading failed.\\n";
}
?>

可以通过图片添加注释来绕过此验证。
如添加注释<?php phpinfo(); ?>,保存图片后将其扩展名改为php,则可成功上传。
上传成功后访问该文件则可看到如下显示

 

文件扩展名验证

 通过黑名单或白名单对文件扩展名进行过滤,如下代码

upload.php

<?php
$blacklist = array(".php", ".phtml", ".php3", ".php4");
foreach ($blacklist as $item) {
if(preg_match("/$item\\$/i", $_FILES[\'userfile\'][\'name\'])) {
    echo "We do not allow uploading PHP files\\n";
    exit;
}
}
$uploaddir = \'uploads/\';
$uploadfile = $uploaddir . basename($_FILES[\'userfile\'][\'name\']);
if (move_uploaded_file($_FILES[\'userfile\'][\'tmp_name\'], $uploadfile)){
    echo "File is valid, and was successfully uploaded.\\n";
} else {
    echo "File uploading failed.\\n";
}
?>

当黑名单不全,构造特殊文件名可以绕过扩展名验证

直接访问上传的文件
将上传文件保存在非web root下其他文件夹下,可以防止用户通过路径直接访问到文件。
upload.php

<?php
$uploaddir = \'d:/uploads/\';
$uploadfile = $uploaddir . basename($_FILES[\'userfile\'][\'name\']);
if (move_uploaded_file($_FILES[\'userfile\'][\'tmp_name\'], $uploadfile)) {
  echo "File is valid, and was successfully uploaded.\\n";
} else {
  echo "File uploading failed.\\n";
}
?>

用户不可以直接通过http://localhost/uploads/ 来访问文件,必须通过view.php来访问
view.php

<?php
$uploaddir = \'d:/uploads/\';
$name = $_GET[\'name\'];
readfile($uploaddir.$name);
?>

查看文件代码未验证文件名,用户可以通过例如http://localhost/view.php?name=..//php/upload.php,查看指定的文件


解决漏洞示例

upload.php

<?php
require_once \'DB.php\';
$uploaddir = \'D:/uploads/\'; 
$uploadfile = tempnam($uploaddir, "upload_");
if (move_uploaded_file($_FILES[\'userfile\'][\'tmp_name\'], $uploadfile)) {
  $db =& DB::connect("mysql://username:password@localhost/database");
  if(PEAR::isError($db)) {
    unlink($uploadfile);
    die "Error connecting to the database";
  }
  $res = $db->query("INSERT INTO uploads SET name=?, original_name=?,mime_type=?",
      array(basename($uploadfile,basename($_FILES[\'userfile\'][\'name\']),$_FILES[\'userfile\'][\'type\']));   if(PEAR::isError($res)) {     unlink($uploadfile);     die "Error saving data to the database. The file was not uploaded";   }   $id = $db->getOne(\'SELECT LAST_INSERT_ID() FROM uploads\');   echo "File is valid, and was successfully uploaded. You can view it <a href=\\"view.php?id=$id\\">here</a>\\n"; } else {   echo "File uploading failed.\\n"; } ?>

  view.php

<?php
require_once \'DB.php\';
$uploaddir = \'D:/uploads/\';
$id = $_GET[\'id\'];
if(!is_numeric($id)) {
  die("File id must be numeric");
}
$db =& DB::connect("mysql://root@localhost/db");
if(PEAR::isError($db)) {
  die("Error connecting to the database");
}
$file = $db->getRow(\'SELECT name, mime_type FROM uploads WHERE id=?\',array($id), DB_FETCHMODE_ASSOC);
if(PEAR::isError($file)) {
  die("Error fetching data from the database");
}
if(is_null($file) || count($file)==0) {
  die("File not found");
}
header("Content-Type: " . $file[\'mime_type\']);
readfile($uploaddir.$file[\'name\']);
?>

上述代码文件名随机更改,文件被存储在web root之外,用户通过id在数据库中查询文件名,读取文件,可以有效的阻止上述漏洞发生

总结

通过以上示例分析,可总结一下几点

1.文件名修改,不使用用户上传的文件名

2.用户不可以通过上传路径直接访问文件

3.文件查看采用数据库获取文件名,从而在相应文件服务器读取文件

4.文件上传限制文件大小,个人上传数量等

以上是关于文件上传漏洞的主要内容,如果未能解决你的问题,请参考以下文章

文件上传漏洞

php代码审计8审计文件上传漏洞

2020/1/30 PHP代码审计之文件上传漏洞

文件上传漏洞成因是啥?

文件上传漏洞,解析漏洞总结

文件上传漏洞