上传文件
如果你希望用户将文件上传到你的服务器,你需要在将上传的文件实际移动到你的 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 个目录移动到另一个目录则完全没问题,我们会做得对吗? - 现在你可能会认为你在脚本中正确验证了文件扩展名,但是这个漏洞依赖于 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 类型列入白名单。
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));