为了让最终用户将文件上传到您的网站,就像是给危及您的服务器的恶意用户打开了另一扇门。即便如此,在今天的现代互联网的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服务器执行:
案例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" /> <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>
这段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应用中实现文件上传表单时,应当尊徐正确的安全指导,并且做恰当的测试。不幸的是要做足够的测试将会需要很多时间和更多的安全专家。
还好有了Acunetix WVS,不需要安全专家就可以自动完成上传表单脆弱性检查,Acunetix WVS用最少的时间为开发者提供了足够多的能够追踪并修复问题的信息。
本文地址:http://www.oschina.net/translate/upload-forms-threat
原文地址:http://www.acunetix.com/websitesecurity/upload-forms-threat/
Why File Upload Forms are a Major Security Threat
To allow an end user to upload files to your website, is like opening another door for a malicious user to compromise your server. Even though, in today’s modern internet web applications, it is a common requirement, because it helps in increasing your business efficiency. File uploads are allowed in social network web applications, such as Facebook and Twitter. They are also allowed in blogs, forums, e-banking sites, YouTube and also in corporate support portals, to give the opportunity to the end user to efficiently share files with corporate employees. Users are allowed to upload images, videos, avatars and many other types of files.
The more functionality provided to the end user, the greater is the risk of having a vulnerable web application and the chance that such functionality will be abused from malicious users, to gain access to a specific website, or to compromise a server is very high.
While testing several web applications, we noticed that a good number of well known web applications, do not have secure file upload forms. Some of these vulnerabilities were easily exploited, and we could gain access to the file system of the server hosting these web applications. In this whitepaper, we present you with 8 common ways we encountered of securing file upload forms. We also show how a malicious user, can easily bypass such security measures.
Case 1: Simple File Upload Form with no Validation
A simple file upload form usually consists of a HTML form and a PHP script. The HTML form, is the form presented to the user, while the PHP script contains the code that takes care of the file upload. Below is an example of such form and PHP script:
HTML Form:
<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 Code:
-
<?php
-
$target_path = “uploads/”;
-
$target_path = $target_path . basename($_FILES[‘uploadedfile’][‘name’]);
-
if (move_uploaded_file($_FILES[‘uploadedfile’][‘tmp_name’], $target_path)) {
-
} else {
-
echo “There was an error uploading the file, please try again!”;
-
}
-
?>
When PHP receives a POST request with encoding type multipart/form-data, it will create a temporary file with a random name in a temp directory (e.g. /var/tmp/php6yXOVs). PhP will also populate the global array $_FILES with the information about the uploaded file:
- $_FILES[‘uploadedfile’][‘name’]: The original name of the file on the client machine
- $_FILES[‘uploadedfile’][‘type’]: The mime type of the file
- $_FILES[‘uploadedfile’][‘size’]: The size of the file in bytes
- $_FILES[‘uploadedfile’][‘tmp_name’]: The temporary filename in which the uploaded file was stored on the server.
The PHP function move_uploaded_file will move the temporary file to a location provided by the user. In this case, the destination is below the server root. Therefore the files can be accessed using a URL like: http://www.domain.tld/uploads/uploadedfile.ext. In this simple example, there are no restrictions about the type of files allowed for upload and therefore an attacker can upload a PHP or .NET file with malicious code that can lead to a server compromise.
This might look like a naïve example, but we did encounter such code in a number of web applications.
Case 2: Mime Type Validation
Another common mistake web developers make when securing file upload forms, is to only check for mime type returned from PHP. When a file is uploaded to the server, PHP will set the variable $_FILES[‘uploadedfile’][‘type’] to the mime-type provided by the web browser the client is using. However, a file upload form validation cannot depend on this value only. A malicious user can easily upload files using a script or some other automated application that allows sending of HTTP POST requests, which allow him to send a fake mime-type.
Case 3: Block Dangerous Extensions
In other cases, we encountered file upload forms using a blacklist approach, as a security measure. A list of dangerous extensions is compiled from the developer, and the access is denied if the extension of the file being uploaded is on the compiled list.
One main disadvantage of using black listing of file extensions, is that it is almost impossible to compile a list that includes all possible extensions that an attacker can use. E.g. If the code is running in a hosted environment, usually such environments allow a large number of scripting languages, such as Perl, Python, Ruby etc, and the list can be endless.
A malicious user can easily bypass such check by uploading a file called “.htaccess”, which contains a line of code similar to the below:
AddType application/x-httpd-php .jpg
The above line of code, instructs Apache web server to execute jpg images as if they were PHP scripts. The attacker can now upload a file with a jpg extension, which contains PHP code. As seen in the screen shot below, requesting a jpg file which includes the PHP command phpinfo() from a web browser, it is still executed from the web server:
Case 4: Double extensions (part 1)
This case’s security measures, as a concept are very similar to that one used in case 3. Though instead of simply checking the extension string present in the filename, the developer is extracting the file extension by looking for the ‘.’ character in the filename, and extracting the string after the dot character.
The method used to bypass this approach is a bit more complicated, but still realistic. First, let’s have a look at how Apache handles files with multiple extensions. A quote from the Apache manual states:
“Files can have more than one extension, and the order of the extensions is normally irrelevant. For example, if the file welcome.html.fr maps onto content type text/html and language French then the file welcome.fr.html will map onto exactly the same information. If more than one extension is given which maps onto the same type of meta-information, then the one to the right will be used, except for languages and content encodings. For example, if .gif maps to the MIME-type image/gif and .html maps to the MIME-type text/html, then the file welcome.gif.html will be associated with the MIME-type text/html.”
Therefore a file named ‘filename.php.123’, will be interpreted as a PHP file and will be executed. This only works if the last extension (in our case .123), is not specified in the list of mime-types known to the web server. Web developers, usually are not aware of such ‘feature’ in Apache, which can be very dangerous for a number of reasons. Knowing this, an attacker can upload a file named shell.php.123 and bypass the file upload form protection. The script will compute the last extension (.123), and concludes that this extension is not in the list of dangerous extension. Having said that, it is impossible to predict all the possible random extensions a malicious user will use to be able to upload a file on your web server.
Case 5: Double extensions (part 2)
A better approach to securing file upload forms is the white list approach. In this case, the developer defines a list of known/accepted extensions and does not allow extensions that are not specified in the list.
However, in some cases this approach will not work as expected. When Apache is configured to execute PHP code, there are 2 ways one can specify this: to use the AddHandler directive, or to use the AddType directive. If AddHandler directive is used, all filenames containing the ‘.php’ extension (e.g. ‘.php’, ‘.php.jpg’) will be executed as a PHP script. Therefore, if your Apache configuration file contains the following line, you may be vulnerable:
AddHandler php5-script .php
An attacker can upload a file named ‘filename.php.jpg’ and bypass the protection, and will be able to execute the code.
Case 6: Checking the image header
When images only are allowed to be uploaded, developers usually validate the image header by using the PHP function called getimagesize. When called, this function will return the size of an image. If the image validation is invalid, which means that the header is incorrect, the function will return a false. Therefore a developer typically checks if the function returns a true or false, and validate the uploaded file using this information. So, if a malicious user tries to upload a simple PHP shell embedded in a jpg file, the function will return false and he won’t be allowed to upload the file. However, even this approach can be easily bypassed. If a picture is opened in an image editor, like Gimp, one can edit the image comment, where PHP code is inserted, as shown below.
The image will still have a valid header; therefore it bypasses the getimagesize PHP check. As seen in the screen shot below, the PHP code inserted in the image comments still gets executed when the image is requested from a normal web browser:
Case 7: Protecting the upload folder with .htaccess
Another popular way of securing file upload forms, is to protect the folder where the files are uploaded using .htaccess file. The idea is to restrict execution of script files in this folder. A .htaccess file typically contains the below code when used in this kind of scenario:
AddHandler cgi-script .php .php3 .php4 .phtml .pl .py .jsp .asp .htm .shtml .sh .cgi
Options –ExecCGI
The above is another type of blacklist approach, which in itself is not very secure. In the PHP manual, in the move_uploaded_file section, there is a warning which states ‘If the destination file already exists, it will be overwritten.’
Because uploaded files can and will overwrite the existing ones, a malicious user can easily replace the .htaccess file with his own modified version. This will allows him to execute specific scripts which can help him compromise the server.
Case 8: Client-side validation
Another common type of security used in file upload forms, is client-side validation of files to be uploaded. Typically, such approach is more common in ASP.NET applications, since ASP.NET offers easy to use validation controls.
These types of validation controls, allow a developer to do regular-expression checks upon the file that is being uploaded, to check that the extension of the file being uploaded is specified in the list of allowed extensions. Below is a sample code, taken from the Microsoft’s website:
-
<asp:FileUpload ID=”FileUpload1″ runat=”server” />
-
<asp:Button ID=”Button1″ runat=”server” OnClick=”Button1_Click” Text=”Upload File” />
-
<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>
-
This ASP.NET code uses validation controls, so the end user is only allowed to upload .mp3, .mpeg or .m3u files to the web server. If the file type does not match any of the 3 specified extensions, the validation control throws an exception and the file won’t be uploaded.
Because this type of validation is done on the client side, a malicious user can easily bypass this type of validation. It is possible to write a short client side script that will do the validation instead of the script provided by the web application. Without using a web browser, the attacker can use an application that allows sending of HTTP POST requests to be able to upload the file.
Suggested Solution
Below is a list of best practices that should be enforced when file uploads are allowed on websites and web applications. These practices will help you securing file upload forms used in web applications;
-
Define a .htaccess file that will only allow access to files with allowed extensions.
-
Do not place the .htaccess file in the same directory where the uploaded files will be stored. It should be placed in the parent directory.
-
A typical .htaccess which allows only gif, jpg, jpeg and png files should include the following (adapt it for your own need). This will also prevent double extension attacks.
deny from all
<Files ~ “^\w+\.(gif|jpe?g|png)$”>
order deny,allow
allow from all
</Files>
-
If possible, upload the files in a directory outside the server root.
-
Prevent overwriting of existing files (to prevent the .htaccess overwrite attack).
-
Create a list of accepted mime-types (map extensions from these mime types).
-
Generate a random file name and add the previously generated extension.
-
Don’t rely on client-side validation only, since it is not enough. Ideally one should have both server-side and client-side validation implemented.
Conclusion
As seen above, there are many ways how a malicious user can bypass file upload form security. For this reason, when implementing a file upload form in a web application, one should make sure to follow correct security guidelines and test them properly. Unfortunately, to perform the number of tests required, can take a lot of time and require a good amount of web security expertise.
Though with Acunetix WVS, one can automatically perform file upload forms vulnerability tests without the need of web security expertise. Acunetix WVS provides the developer with extensive amount of information to be able to trace and fix the problem in the least possible time.
Visit http://www.acunetix.com/vulnerability-scanner/ for more information on Acunetix Web Vulnerability Scanner.
Bogdan Calin, May 2009
Download this whitepaper as PDF.
转载请注明:jinglingshu的博客 » 为什么文件上传表单是主要的安全威胁