为什么文件上传表单是主要的安全威胁


用户将文件上传到您的网站就像是给危及您的服务器的恶意用户打开了另一扇门即便如此,在今天的现代互联网的Web应用程序是一种常见的要求因为它有助于提高您的业务效率Facebook和Twitter社交网络的Web应用程序允许文件上传也让他们博客,论坛,电子银行网站YouTube和企业支持门户,给机会给最终用户企业员工有效地共享文件允许用户上传图片,视频,头像和许多其他类型的文件

向终端用户提供的功能越多Web应用受到攻击的风险机会就越大,这种功能恶意用户利用获得到一个特定网站的权限,危及服务器的可能性是非常高的

当在测试几个Web应用程序,我们注意到,相当多的知名Web应用程序,不具备安全文件上传形式这些漏洞很容易被利用,我们可以访问这些Web应用程序的服务器的文件系统在这篇文章中我们为您介绍8种常见方式我们遇到过的安全文件上传表单同时,还将展示一个恶意用户可以轻松地绕过这些安全措施

情形一: 简单的、没有验证的文件上传表单

一个简单的文件上传表单常常包含一些HTML表单和PHP脚本。HTML表单以页面的形式展现给用户,而PHP脚本则负责处理文件上传。下面是一个包含HTML表单和PHP脚本的简单例子:

HTML 表单:

<form enctype="multipart/form-data" action="uploader.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>

PHP 脚本:

<?php
    $target_path  =  "uploads/";
    $target_path  =  $target_path  .  basename($_FILES['uploadedfile']['name']);
    if (move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
    echo "文件 " . basename($_FILES['uploadedfile']['name']) . " 上传成功!";
    } else {
    echo "文件上传遇到错误,请重试!";
    }
?>

当PHP接收到一个 multipart/form-data类型的 POST请求时,PHP会在临时文件夹(例如:/var/tmp/php6yXOVs)创建一个名字随机生成的临时文件。同时,PHP也会把上传文件的信息保存到全局数组 $_FILES当中,格式如下 

  • $_FILES[‘uploadedfile’][‘name’]: 上传文件的原文件名
  • $_FILES[‘uploadedfile’][‘type’]: 上传文件的MIME TYPE
  • $_FILES[‘uploadedfile’][‘size’]: 上传文件的大小(单位为byte)
  • $_FILES[‘uploadedfile’][‘tmp_name’]: 上传文件在服务器上临时存放的路径

PHP函数move_uploaded_file用户提供的临时文件移动到一个位置在这种情况下,目的地是服务器根目录以下因此,文件可以使用的URL,如:http://www.domain.tld/uploads/uploadedfile.ext访问在这个简单例子,有允许上传文件类型没有限制,因此攻击者可以上传一个带有恶意代码的PHP或.NET文件,可导致服务器被攻击

可能看起来像很幼稚例子但我们在一些Web应用中还真的遇到过这样的代码

案例2:Mime类型验证

另一个常见的错误Web开发人员确保文件上传表单时,只检查从PHP返回mime类型。当一个文件被上传到服务器,PHP将设置变量$_FILES[‘UploadedFile’][‘type’]所提供的Web浏览器客户端使用的MIME类型。然而,文件上传表单验证不能依赖于这个值。恶意用户可以轻松地使用脚本或其他一些自动化的应用程序,允许发送HTTP POST请求,这让他送一个假的mime类型的文件上传。

案例3:限制危险的拓展

在另一个例子里,我们遇到了文件上传使用黑名单的做法,作为一项安全措施。从开发者收集制定的危险列表,如果正在上传的文件包含在列表中,访问会被拒绝。

使用危险的文件扩展名,其主要缺点之一是,它几乎不可能编制一份完整的清单,包括攻击者可以使用的所有可能的扩展名。例如如果代码运行在托管环境中,通常这样的环境让大量的脚本语言,如Perl,Python和Ruby等,列表可以是无穷无尽的。

恶意用户可以很容易地绕过该检查上传一个文件名为“.htaccess”,其中包含类似于下面的一行代码:

AddType application/x-httpd-php .jpg

上面的代码行,指示Apache Web服务器执行jpg图片,好像他们是PHP脚本。攻击者现在可以上传一个jpg扩展名的文件,其中包含PHP代码。正如下面的截图,通过web浏览器请求一个jpg文件,其中包含PHP的命令phpinfo()函数,它仍然是从Web服务器执行:

Why File Upload Forms are a Security Threat

案例4: 双扩展名 (第1部分)

本案例中使用的安全策略和案例3中所使用的非常相近. 尽管方式换成了简单的检查文件名具有的扩展名, 开发者通过在文件名中查找 ‘.’ 字符并提取点号之后的字符串来得到文件扩展名.

绕过该途径的方法有点儿复杂, 但是仍然是现实的. 首先, 让我们看看 Apache 是怎么处理具有多重扩展名的文件的. Apache 手册中有如下一段陈述:

“文件可以有多个扩展名, 这些扩展名的顺序一般情况下是无关紧要的. 例如: 如果文件 welcome.html.fr 被映射为内容类型是 text/html , 语言是法语的话, 文件welcome.fr.html 将被映射为完全相同的内容. 如果一个以上的扩展名映射到同种类型的元信息上, 那么将使用最右边的那个, 除了语言和内容编码. 比如: .gif 的 MIME 类型是 image/gif , .html 的 MIME 类型是 text/html , 那么 welcome.gif.html 的 MIME 类型将是text/html ."

因此一个名为 ‘filename.php.123’ 的文件将会被解释为一个 PHP 文件并被执行. 这仅限于最后的那个扩展名(本例中是 .123)没有在 web 服务器的 mime-types 列表中被指定. web开发者通常不会意识到 Apache 还存在这么一个 ‘特性’, 出于某些原因来说这可能非常危险. 知道了这个以后, 一个攻击者可以上传一个名为 shell.php.123 的文件并绕过文件上传保护机制. 后台脚本将会计算出最后的扩展名 (.123)并作出该扩展名并不在危险的扩展名列表内的结论. 话虽如此, 想要预防某恶意用户可能会使用的所有随机扩展名来上传一个文件到你的 web 服务器上是不可能的。

案例5: 双扩展名 (第2部分)

一个更好的增强文件上传表单的安全性的途径就是白名单机制. 在本例中, 开发者定义了一个 已知/可接受 的扩展名列表并且不允许使用未在名单中指定的扩展名.

然而, 在某些情况下该途径不会像期待的方式那样工作. 当 Apache 被配置为执行 PHP 代码的时候, 存在两种方式来实现该机制: 使用 AddHandler 指令, 或者使用 AddType 指令. 如果 AddHandler 指令被使用, 所有包含 ‘.php’ 扩展名的文件名(例如: ‘.php’ , ‘.php.jpg’)均被作为 PHP 脚本来执行. 因此, 如果你的 Apache 配置文件包含如下一行的话, 你可能很容易受到攻击:

AddHandler php5-script .php

一个攻击者可以上传名为 ‘filename.php.jpg’ 的文件并绕过保护机制, 然后执行其中的代码.

案例 6: 检查图片头部

当仅允许上传图片的时候, 开发者通常使用 PHP 的 getimagesize 函数来检测图片的头部信息. 该函数在被调用时将会返回图片的尺寸, 如果图片经验证为无效的, 也就是说图片头部信息不正确, 则会返回 false 值. 因此一个开发者一般会检查该函数是否返回 true 或 false, 并且通过该信息来验证上传的文件. 所以, 如果一个恶意用户试着上传一个内嵌有简单 PHP shell 的 jpg 文件的话, 该函数会返回 false 然后他将不允许上传此文件. 然而, 即使这种方式也能被很容易的绕过. 如果一个图片在一个图片编辑器内打开, 就如 Gimp, 用户就可以编辑图片的注释区, 那儿就能插入 PHP 代码, 就如下图所示.

该图片仍然有一个有效的头部; 因此就绕过了 getimagesize 函数的检查. 从下面截图中可以看到, 当一个普通的 web 浏览器请求该图的时候, 插入到图片注释区的 PHP 代码仍然被执行了:

案例七:通过.htaccess保护上传文件夹

另一种流行的穿件安全的文件上传表单的方法是适用.htaccess保护好上传文件存放的文件夹。办法是限制这个文件夹里的脚本文件的执行。这种情形一下,一个.htaccess文件一般包含下面的代码:

AddHandler cgi-script .php .php3 .php4 .phtml .pl .py .jsp .asp .htm .shtml .sh .cgi
Options –ExecCGI

上面的是另一种形式的黑名单,本身并不是很安全。在PHP手册中,move_uploaded_file一章中,有一个warning:若目标文件已经存在,则会覆盖原文件。

因为上传的文件能够而且会覆盖已经存在的同名文件,一个恶意用户很轻易就能用他自己修改过的.htaccess替换掉原来的。这使得他可以执行特定的将会帮助他危害服务器的脚本。

案例八:客户端验证

另一种在文件上传表单中常用的安全技术是在客户端验证上传的文件。一般而言,该技术在ASP.NET应用中更通用一些,因为ASP.NET提供了易用的验证控件。

这些验证控件允许开发者对要上传的文件做正则检查,以查出待上传的文件扩展名是否在允许列表中。下面是一段来自微软网站的示例代码:

<asp:FileUpload ID="FileUpload1" runat="server" />

  <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Upload File" />&nbsp;

  <asp:Label ID="Label1" runat="server"></asp:Label>
  <asp:RegularExpressionValidator id="RegularExpressionValidator1" runat="server"
  ErrorMessage="Only mp3, m3u or mpeg files are allowed!"
  ValidationExpression="^(([a-zA-Z]:)|(\{2}w+)$?)(\(w[w].*))
  +(.mp3|.MP3|.mpeg|.MPEG|.m3u|.M3U)$" ControlToValidate="FileUpload1"></asp:RegularExpressionValidator>

  <asp:RequiredFieldValidator id="RequiredFieldValidator1" runat="server"
  ErrorMessage="This is a required field!"
  ControlToValidate="FileUpload1"></asp:RequiredFieldValidator>
  &nbsp;

这段ASP.NET代码使用了验证控件,所以最终用户只被允许上传.mp3,.mpeg,或者.m3u文件到服务器。若文件类型和这三个指定的文件类型不一致,验证控件将跑出异常,文件也就不会被上传。

由于这种文件验证是在客户端完成的,恶意用户很容易就能绕过这一检查。写一段客户端脚本来替换web应用的验证脚本做验证并非不可能。不用web浏览器,入侵者可以使用可以发送HTTP POST请求的程序来实现上传文件。

推荐的解决方案

在允许上传文件的网站和web应用中,应当应用下面的一系列最佳实践方法。这些实践方法将有助于你保证web应用的上传文件的安全性。

  • 定义一个.htaccess文件,只允许访问指定扩展名的文件。
  • 不要把.htaccess文件和上传文件放在同一个目录里,应该放在父目录里。
  • 一个典型的只允许 gif, jpg, jpeg 和 png文件的.htaccess文件应当包含下面的代码(根据你的需求做调整)。这样也能阻止双扩展名攻击。

deny from all
<Files ~ “^w+.(gif|jpe?g|png)$”>
order deny,allow
allow from all
</Files>

  • 如果可能,把文件上传到root目录以外的目录里。
  • 禁止覆盖已存在的文件(以阻止.htaccess覆盖攻击)
  • 创建一个mime-type白名单列表。(只允许这个列表里的Mime-type)
  • 生成一个随机的文件名,并且加上此前生成的文件扩展名、
  • 不要只依赖客户端验证,这不够。理想的是既有客户端验证也有服务器端验证。

总结

如上所述,恶意用户有很多手段绕过文件上传表单安全验证。因此,在web应用中实现文件上传表单时,应当遵循正确的安全指导,并且做恰当的测试。所以可以使用WVS协助进行安全测试。

译文链接:为什么文件上传表单是主要的安全威胁

原文链接:Why File Upload Forms are a Major Security Threat

配置原则:
服务器端,Web用户权限:有写入权限的目录,不给动态脚本的执行权,有执行权的目录,不给写入权。

{上传的文件最好是不要放到Web应用的目录下,而是放在另外的一个独立的空间,然后通过可控制的代码访问文件的内容。}

之前CasperKid写过一篇PDF文档,详细的说明了文件上传攻击的一个大体框架,两相对比着看效果会好些:Upload_Attack_Framework


《 “为什么文件上传表单是主要的安全威胁” 》 有 7 条评论

  1. 上传绕过
    https://tom0li.github.io/blog/bypass-upload.html
    `
    常见绕过姿势
     客户端绕过(burp 改包)
     Boundary边界(不一致)
     服务端
      文件类型MIME(检测Content-Type)
      文件后缀
      文件内容
      服务端目录路径(path相关的内容)
     配合文件包含
     服务器解析漏洞
     CMS,编辑器漏洞
     操作系统文件名规则
     WAF
    `

  2. 构造优质上传漏洞fuzz字典
    http://gv7.me/articles/2018/make-upload-vul-fuzz-dic/
    https://www.freebuf.com/articles/web/188464.html
    https://github.com/c0ny1/upload-fuzz-dic-builder
    `
    上传漏洞的利用姿势很多,同时也会因为语言,中间件,操作系统的不同,利用也不同。比如有:大小写混合,.htaccess,解析漏洞,00截断,.绕过,空格绕过,::$DATA绕过,以及多种姿势的组合等等。当遇到一个上传点,如何全面的利用以上姿势测试一遍,并快速发现可以成功上传webshell的姿势?

    方案一:一个一个手工测试
    手工把所有姿势测试一遍,先不说花费大量时间,还很可能会遗漏掉某些姿势而导致无法利用成功。

    方案二:fuzz
    在fuzz时我们往往会给一个输入点喂入大量特殊的数据。这个特殊的数据可能随机的,毫无规律的,甚至我们都无法预知的。但我思考了一下,这样的fuzz方式只是适合在本地fuzz 0day漏洞,并不适合通过fuzz在线网站的上传点,快速找出可以成功上传webshell的payload,因为时间成本排在哪里。

    通过思考,我们可以知道如果能根据上传漏洞的场景(后端语言,中间件,操作系统)来生成优质的fuzz字典,然后使用该字典进行fuzz,就能消除以上两个解决方案的弊端!
    `

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注