PHP图片上传安全检查清单

69

我正在编写一个脚本来上传图片到我的应用程序,下面的安全步骤能够保证我的应用程序在脚本方面的安全性吗?

  • 使用.httaccess禁止PHP在上传文件夹内运行。
  • 如果文件名包含字符串"php",则不允许上传。
  • 仅允许以下扩展名:jpg、jpeg、gif 和 png。
  • 仅允许图像文件类型。
  • 不允许包含两种文件类型的图像。
  • 更改图像名称。
  • 上传到子目录而不是根目录。

这是我的脚本:

 $filename=$_FILES['my_files']['name'];
 $filetype=$_FILES['my_files']['type'];
 $filename = strtolower($filename);
 $filetype = strtolower($filetype);

 //check if contain php and kill it 
 $pos = strpos($filename,'php');
 if(!($pos === false)) {
  die('error');
 }




 //get the file ext

 $file_ext = strrchr($filename, '.');


 //check if its allowed or not
 $whitelist = array(".jpg",".jpeg",".gif",".png"); 
 if (!(in_array($file_ext, $whitelist))) {
    die('not allowed extension,please upload images only');
 }


 //check upload type
 $pos = strpos($filetype,'image');
 if($pos === false) {
  die('error 1');
 }
 $imageinfo = getimagesize($_FILES['my_files']['tmp_name']);
 if($imageinfo['mime'] != 'image/gif' && $imageinfo['mime'] != 'image/jpeg'&& $imageinfo['mime']      != 'image/jpg'&& $imageinfo['mime'] != 'image/png') {
   die('error 2');
 }
//check double file type (image with comment)
if(substr_count($filetype, '/')>1){
die('error 3')
}

 // upload to upload direcory 
 $uploaddir = 'upload/'.date("Y-m-d").'/' ;

if (file_exists($uploaddir)) {  
} else {  
    mkdir( $uploaddir, 0777);  
}  
  //change the image name
 $uploadfile = $uploaddir . md5(basename($_FILES['my_files']['name'])).$file_ext;



  if (move_uploaded_file($_FILES['my_files']['tmp_name'], $uploadfile)) {
 echo "<img id=\"upload_id\" src=\"".$uploadfile."\"><br />";
  } else {
   echo "error";
  }

欢迎提供任何新的技巧 :)


13
我会移除以下规则: 如果文件名包含字符串“php”,则不允许上传。 这个规则是不必要的,因为你已经在重命名文件。 - troynt
1
您可以从Github下载安全图片上传。这是目前最安全的PHP脚本,还支持图片大小调整/裁剪功能。 - samayo
1
@Alez 从快速浏览该类,我唯一能看到的安全性是一个扩展检查。拜托,拜托,不要这样! - sousdev
@Fricker 嗯,如果你不够聪明,没有给你的文件夹执行文件的权限,那我同意。你应该不睡觉 :) - samayo
3
当然,顺便说一下我喜欢路加福音3:11的许可证 :) - sousdev
显示剩余6条评论
12个回答

36

使用GD(或Imagick)重新处理图像并保存处理后的图像。对于黑客来说,其他所有操作只是有趣的无聊的。

编辑:正如rr所指出的那样,在任何上传中都要使用move_uploaded_file()

最后编辑:顺便说一下,您需要非常限制上传文件夹。这些地方是许多漏洞发生的黑暗角落之一。这适用于任何类型的上传和任何编程语言/服务器。请检查https://www.owasp.org/index.php/Unrestricted_File_Upload


2
+1. 如果它是一张图片并且包含有效的扩展名,我不明白问题列表中的任何检查如何有用。 - Arseni Mourzenko
7
记住我的话:大部分时间,CPU 是最多余的资源。只需观察任何一台机器/服务器的图形(除了 Google、Facebook 等服务器可能除外),你会发现它大部分时间都在闲置。像 SETI@Home 这样的项目尝试利用这些空闲时间。无论如何,为了增加安全性,付出这些努力肯定是值得的。 - Halil Özgür
2
你能举个“重新处理”的例子吗?比如说用Imagick(或者最差情况下是GD)? - Andrew
1
仅仅检查getimagesize()的结果对于“处理图像”来说是否有效?如果不是有效的图像,getimagesize()会返回false,对吗?只是为了让大家清楚,通过发布虚假的mime类型和/或扩展名来绕过安全检查非常简单。 - thorne51
2
@thorne51,我认为没有足够的函数可以进行检查。所以不要检查,只需创建一个新图像。例如:PHP代码可以隐藏在图像的EXIF、XMP等部分中,而函数/程序可能会失败(无意或通过利用代码)报告该代码。 - Halil Özgür
显示剩余8条评论

13

对于图像文件的安全测试,我可以考虑四个安全级别。它们分别是:

  • 级别1:检查扩展名(文件扩展名)
  • 级别2:检查MIME类型 ($file_info = getimagesize($_FILES ['image_file']; $file_mime = $file_info [ 'mime' ];)
  • 级别3:读取前100个字节,并检查它们是否有以下范围内的任何字节:ASCII 0-8、12-31(十进制)。
  • 级别4:检查标题中的魔数(文件的前10-20个字节)。您可以从这里找到一些文件标题字节:http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Examples

注意:加载整个图像将会很慢。


3和4不会特别慢。慢的是将整个图像加载到库中进行验证。 - James Billingham

9

XSS警告

还有一个非常重要的提示。不要上传任何可以在浏览器中解释为HTML的内容。

由于文件位于您的域上,包含在HTML文档中的JavaScript将可以访问所有cookie,从而可能导致某种XSS攻击。

攻击场景:

  1. 攻击者上传带有JS代码的HTML文件,该代码将所有cookie发送到他的服务器。

  2. 攻击者通过电子邮件、PM或仅通过在他的或任何其他站点上使用iframe向您的用户发送链接。

最安全的解决方案:

只在子域或另一个域上提供已上传的内容。这样,cookie将无法访问。这也是谷歌的性能提示之一:

https://developers.google.com/speed/docs/best-practices/request#ServeFromCookielessDomain


7
在上传目录中创建一个新的.htaccess文件,并粘贴以下代码:
php_flag engine 0
RemoveHandler .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml
AddType application/x-httpd-php-source .phtml .php .php3 .php4 .php5 .php6 .phps .cgi .exe .pl .asp .aspx .shtml .shtm .fcgi .fpl .jsp .htm .html .wml

请确保重命名上传的文件,不必检查文件类型、内容等。


1
你确定在图像 POST 数据的 PHP 脚本中不需要进行其他检查吗? - Yuda Prawira

6

5

我将重复我在相关问题中发布的内容。

您可以使用Fileinfo函数(在旧版本的PHP中为mime_content_type())来检测内容类型。

以下是关于旧版Mimetype扩展的PHP手册摘录,现在已被Fileinfo替代:

该模块中的函数尝试通过查找文件内特定位置处的某些幻数字节序列来猜测文件的内容类型和编码。虽然这不是一种百分之百准确的方法,但所使用的启发式方法做得很好。

getimagesize()也可能很好地完成此任务,但您执行的大多数其他检查都是无意义的。例如,为什么文件名中不允许字符串php。您不会仅因其名称包含php字符串而在PHP脚本中包含图像文件,对吗?


当谈到重新创建图像时,在大多数情况下,这将提高安全性……直到您使用的库不易受攻击为止。

那么哪个PHP扩展最适合用于安全的图像重新创建?我查看了CVE details网站。我认为适用的三个扩展名如下:

  1. GD(6个漏洞)
  2. ImageMagick(44个漏洞)
  3. Gmagick(12个漏洞)

从比较中我认为GD最合适,因为它的安全问题数量最少且相当老旧。其中三个是关键性的,但ImagMagick和Gmagick表现并不更好…… ImageMagick似乎非常有漏洞(至少在安全方面),所以我选择Gmagick作为第二个选项。


2

如果安全非常重要,可以使用数据库保存文件名和重命名的文件名,并且在此处可以更改文件扩展名为类似于 .myfile 的东西,并制作一个用于发送带有标头的图像的 php 文件。PHP 可以更加安全,您可以像下面这样在 img 标签中使用它:

<img src="send_img.php?id=555" alt="">

在上传之前,还需要通过EXIF检查文件扩展名。

如果您使用数据库,那么您应该确保首先对文件名进行清理。 - starbeamrainbowlabs

2
在PHP中,允许用户安全上传文件的最简单的答案是: 始终将文件保存在文档根目录之外。 例如:如果您的文档根目录是/home/example/public_html,则将文件保存到/home/example/uploaded
通过这种方式,您的文件安全地脱离了直接被Web服务器执行的限制,您仍然可以通过以下几种方式使它们对访问者可用:
  1. 为提供从不执行PHP、Perl等脚本的静态内容的单独虚拟主机设置。
  2. 将文件上传到另一台服务器(例如便宜的VPS,Amazon S3等)。
  3. 将其保留在同一服务器上,并使用一个PHP脚本作为代理来确保文件只能读取,而不能执行。
然而,如果您选择此列表中的选项1或3,并且您的应用程序存在本地文件包含漏洞,则您的文件上传表单仍然可能成为攻击向量。

2

在用户上传图片时保持网站安全的最佳方法是:

  • 检查图片扩展名
  • 使用函数“getimagesize()”检查图片大小
  • 之后可以使用函数“file_get_contents()”
  • 最后,将文件内容插入到数据库中。

我认为这是最好的方法!你有什么看法吗?


数据库并不一定指数据库,它可以是您的公共路径或存储系统。 - HamzaO

1
我昨天寻找最佳安全解决方案,似乎使用额外的应用程序如GD是其中之一 - 但如果某人缺乏好服务器的资金,可能会导致服务器响应巨大的延迟。然而,我想出了一个诀窍,但只有在您没有画廊或某些图像预览,并且您只提供下载上传文件的选项时才可能发生。所以要做的事情是:首先,在飞行中更改名称,然后压缩文件,然后删除源文件 - 这样就不会有任何人可以导致隐藏代码的执行 - 当然,这个解决方案仅适用于服务器安全 - 因为下载文件的用户可能会打开某些受感染的文件等 - 但在我的情况下,这不是问题。

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