上传文件

如果你希望用户将文件上传到你的服务器,你需要在将上传的文件实际移动到你的 Web 目录之前进行一些安全检查。

上传的数据:

此数组包含用户提交的数据,而不是有关文件本身的信息。虽然通常这些数据是由浏览器生成的,但是可以使用软件轻松地对同一表单发布请求。

$_FILES['file']['name'];
$_FILES['file']['type'];
$_FILES['file']['size'];
$_FILES['file']['tmp_name'];
  • name - 验证它的每个方面。
  • type - 切勿使用此数据。它可以通过使用 PHP 函数来获取。
  • size - 使用安全。
  • tmp_name - 使用安全。

利用文件名

通常,操作系统不允许文件名中的特定字符,但通过欺骗请求,你可以添加它们以允许意外事件发生。例如,让我们命名文件:

../script.php%00.png

好好看看那个文件名,你应该注意到一些事情。

  1. 首先要注意的是 ../,在文件名中是完全非法的,同时如果你将文件从 1 个目录移动到另一个目录则完全没问题,我们会做得对吗?
  2. 现在你可能会认为你在脚本中正确验证了文件扩展名,但是这个漏洞依赖于 url 解码,将%00 转换为 null 字符,基本上对操作系统说,这个字符串在这里结束,从文件名中删除 .png

所以现在我已经将 script.php 上传到另一个目录,通过简单的验证到文件扩展名。它还绕过 .htaccess 文件,禁止在你的上传目录中执行脚本。

安全地获取文件名和扩展名

你可以使用 pathinfo() 以安全的方式推断名称和扩展名,但首先我们需要替换文件名中不需要的字符:

// This array contains a list of characters not allowed in a filename
$illegal   = array_merge(array_map('chr', range(0,31)), ["<", ">", ":", '"', "/", "\\", "|", "?", "*", " "]);
$filename  = str_replace($illegal, "-", $_FILES['file']['name']);

$pathinfo  = pathinfo($filename);
$extension = $pathinfo['extension'] ? $pathinfo['extension']:'';
$filename  = $pathinfo['filename']  ? $pathinfo['filename']:'';

if(!empty($extension) && !empty($filename)){
  echo $filename, $extension;
} else {
  die('file is missing an extension or name');
}

虽然现在我们有一个可用于存储的文件名和扩展名,但我仍然更喜欢将该信息存储在数据库中,并为该文件提供生成的名称,例如,md5(uniqid().microtime())

+----+--------+-----------+------------+------+----------------------------------+---------------------+
| `id` | title  | extension | mime       | size | filename                         | time                |
+----+--------+-----------+------------+------+----------------------------------+---------------------+
| `1`  | myfile | txt       | text/plain | 1020 | 5bcdaeddbfbd2810fa1b6f3118804d66 | 2017-03-11 00:38:54 |
+----+--------+-----------+------------+------+----------------------------------+---------------------+

这将解决文件名中重复文件名和未预见漏洞的问题。它还会导致攻击者猜测该文件的存储位置,因为他或她无法专门针对该文件执行。

Mime 型验证

检查文件扩展名以确定它是什么文件是不够的,因为文件可能命名为 image.png 但可能包含一个 PHP 脚本。通过针对文件扩展名检查上载文件的 mime 类型,你可以验证文件是否包含其名称所指的内容。

你甚至可以更进一步验证图像,这实际上是打开它们:

if($mime == 'image/jpeg' && $extension == 'jpeg' || $extension == 'jpg'){
  if($img = imagecreatefromjpeg($filename)){
    imagedestroy($img);
  } else {
    die('image failed to open, could be corrupt or the file contains something else.');
  }
}

你可以使用内置函数来获取 mime 类型。

白名单列出你的上传

最重要的是,你应该根据每个表单将文件扩展名和 mime 类型列入白名单。

function isFiletypeAllowed($extension, $mime, array $allowed)
{
    return  isset($allowed[$mime]) &&
            is_array($allowed[$mime]) &&
            in_array($extension, $allowed[$mime]);
}

$allowedFiletypes = [
    'image/png'  => [ 'png' ],
    'image/gif'  => [ 'gif' ],
    'image/jpeg' => [ 'jpg', 'jpeg' ],
];

var_dump(isFiletypeAllowed('jpg', 'image/jpeg', $allowedFiletypes));