与Apache相比,Nginx算是后起之秀,但大有赶超之势。百度一番,又谷歌一番,最终只找到了三个已公开的、关于Nginx的解析漏洞,记录如下。
利用方式:
/test.jpg/test.php
测试:
首先准备文件test.jpg,内容为:
在浏览器中访问 http://127.0.0.1/test.jpg 显示图片解析错误。在浏览器中访问 http://127.0.0.1/test.jpg/test.php ,显示:“Access denied.” 。这就有意思了,test.jpg是文件不是目录,test.php更是根本就不存在的文件,访问/test.jpg/test.php没有报404,而是显示“Access denied.” 。
Nginx拿到文件路径(更专业的说法是URI)/test.jpg/test.php后,一看后缀是.php,便认为该文件是php文件,转交给php去处理。php一看/test.jpg/test.php不存在,便删去最后的/test.php,又看/test.jpg存在,便把/test.jpg当成要执行的文件了,又因为后缀为.jpg,php认为这不是php文件,于是返回“Access denied.”。
这其中涉及到php的一个选项:cgi.fix_pathinfo,该值默认为1,表示开启。开启这一选项有什么用呢?看名字就知道是对文件路径进行“修理”。何谓“修理”?举个例子,当php遇到文件路径“/aaa.xxx/bbb.yyy/ccc.zzz”时,若“/aaa.xxx/bbb.yyy/ccc.zzz”不存在,则会去掉最后的“/ccc.zzz”,然后判断“/aaa.xxx/bbb.yyy”是否存在,若存在,则把“/aaa.xxx/bbb.yyy”当做文件“/aaa.xxx/bbb.yyy/ccc.zzz”,若“/aaa.xxx/bbb.yyy”仍不存在,则继续去掉“/bbb.yyy”,以此类推。
该选项在配置文件php.ini中。若是关闭该选项,访问 http://127.0.0.1/test.jpg/test.php 只会返回找不到文件。但关闭该选项很可能会导致一些其他错误,所以一般是开启的。
目前我们还没能成功执行代码,因为新版本的php引入了“security.limit_extensions”,限制了可执行文件的后缀,默认只允许执行.php文件。来做进一步测试。找到php5-fpm配置文件php-fpm.conf,若不知道在哪,可用如下命令搜索:
sudo find / -name php-fpm.conf
我的测试环境中,该文件位于/etc/php5/fpm/php-fpm.conf。修改该文件中的“security.limit_extensions”,添加上.jpg,添加后如下所示:
security.limit_extensions = .php .jpg
这样,php便认为.jpg也是合法的php文件后缀了,再在浏览器中访问 http://127.0.0.1/test.jpg/test.php ,看到php被成功执行,如下图所示:
由上述原理可知,http://127.0.0.1/test.jpg/test.xxx/test.php 也是可以执行的。
上面的测试均在Nginx1.4.6中进行。这一漏洞是由于Nginx中php配置不当而造成的,与Nginx版本无关,但在高版本的php中,由于“security.limit_extensions”的引入,使得该漏洞难以被成功利用。
为何是Nginx中的php才会有这一问题呢?因为Nginx只要一看URL中路径名以.php结尾,便不管该文件是否存在,直接交给php处理。而如Apache等,会先看该文件是否存在,若存在则再决定该如何处理。cgi.fix_pathinfo是php具有的,若在php前便已正确判断了文件是否存在,cgi.fix_pathinfo便派不上用场了,这一问题自然也就不存在了。(2017.08.15:IIS在这一点和Nginx是一样的,同样存在这一问题)
做个小实验,分别访问两个不存在的文件123123.xxx和123123.php,虽然都返回404,但一看页面,也该知这两个文件的处理流程是不同的。
下图是访问123123.xxx的结果,404由Nginx给出:
下图是访问123123.php的结果,404页面和上图不同:
查看错误日志,找到了:
FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream, client: 127.0.0.1, server: localhost, request: "GET /123123.php HTTP/1.1", upstream: "fastcgi://unix:/var/run/php5-fpm.sock:", host: "127.0.0.1"
由此可知Nginx确实只看了后缀就直接把123123.php交给php处理了,这一文件不存在也是php做出的判断。
影响范围:
0.5., 0.6., 0.7 <= 0.7.65, 0.8 <= 0.8.37 ?
利用方式:
/test.jpg%00.php
测试:
服务器为Nginx1.4.6,浏览器中访问 http://127.0.0.1/test.jpg%00.php ,返回“400 Bad Request”,代码未执行,测试失败。实在是安不好又找不到这么老的Nginx,遂放弃测试。
%00截断似乎是一个大类,什么时候有空专门研究下。
CVE-2013-4547是一个还算新的漏洞,影响范围也比较大:
0.8.41~1.4.3, 1.5 <= 1.5.7
顺便一提,截止本文写作时,Nginx的最新版本是1.13.4 。
这一漏洞的原理是非法字符空格和截止符(\0)会导致Nginx解析URI时的有限状态机混乱,危害是允许攻击者通过一个非编码空格绕过后缀名限制。是什么意思呢?举个例子,假设服务器上存在文件:“file.aaa ”,注意文件名的最后一个字符是空格。则可以通过访问:
http://127.0.0.1/file.aaa \0.bbb
让Nginx认为文件“file.aaa ”的后缀为“.bbb”。
来测试下,这次测试在Nginx/1.0.15中进行。首先准备一张图片,命名为“test.html ”,注意,文件名含有空格。然后在浏览器中访问该文件,会得到一个404,因为浏览器自动将空格编码为%20,服务器中不存在文件“test.html%20”。
测试目标是要让Nginx认为该文件是图片文件并正确地在浏览器中显示出来。我们想要的是未经编码的空格和截止符(\0),怎么办呢?使用Burp Suite抓取浏览器发出的请求包,修改为我们想要的样子,原本的URL是:http://192.168.56.101/test.htmlAAAphp ,将第一个“A”改成“20”(空格符号的ASCII码),将第二个“A”改成“00”(截止符),将第三个“A”改成“2e”(“.”的ASCII码),如下图所示:
修改完毕后Forward该请求,在浏览器中看到:
我们已经成功地利用了漏洞!但这有什么用呢?我们想要的是代码被执行。
继续测试,准备文件“test.jpg ”,注意文件名的最后一个字符是空格,文件内容为:
用Burp Suite抓包并修改,原本的URL是:http://192.168.56.101/test.jpg…php ,将jpg后的第一个“.”改为20,第二个“.”改为00,如下图所示:
修改完毕后Forword该请求,在浏览器中看到:
Access denied.
好吧,又是这个。打开Nginx的错误日志,在其中也可以看到:
FastCGI sent in stderr: "Access to the script '/usr/local/nginx/html/test.jpg ' has been denied (see security.limit_extensions)" while reading response header from upstream, client: 192.168.56.102, server: localhost, request: "GET /test.jpg .php HTTP/1.1", upstream: "fastcgi://unix:/var/run/php5-fpm.sock:", host: "192.168.56.101"
这说明Nginx在接收到这一请求后,确实把文件“test.jpg ”当做php文件交给php去执行了,只是php看到该文件后缀为“.jpg ”而拒绝执行。这样,便验证了Nginx确实存在该漏洞。
但不知为何,不管我怎样设置,php都不肯把“test.jpg ”当做php文件执行。看来“security.limit_extensions”威力强大,一招破万法。
CVE-2013-4547还可以用于绕过访问限制,虽然和文件解析漏洞无关,但也记录在这里。
首先在网站根目录下新建一个目录,命名为protected,在目录protected中新建文件s.html,内容随意。然后在Nginx的配置文件中写上:
location /protected/ {
deny all;
}
以禁止该目录的访问。接着在网站根目录下新建一个目录,名为“test ”,目录名的最后一个字符是空格,该目录用于触发漏洞。最后来进行验证,直接访问:
http://127.0.0.1/protected/s.html
返回“403 Forbidden”。利用漏洞访问:
http://127.0.0.1/test /../protected/s.html
成功访问到文件s.html。注意上示URL中的空格,不要将空格编码。
为成功利用漏洞,我们在测试中准备了名字以空格结尾的文件和目录,这是因为在linux中,文件名是可以以空格结尾的。若不准备这样的文件,漏洞可以成功触发,但结果却是404,找不到类似“test.jpg ”这样的文件。而在Windows中,文件名不能以空格结尾,所以Windows程序遇到文件名“test.jpg ”会自动去掉最后的空格,等同于访问“test.jpg”,基于这样的原因,这一漏洞在Windows中会很容易利用。
为验证漏洞,需要安装Nginx。为安装有漏洞的版本,不使用apt-get,而是编译安装。我使用的操作系统是Ubuntu14.04,安装了nginx-1.0.15,需要安装以下依赖:
sudo apt-get install libpcre3 libpcre3-dev
sudo apt-get install zlib1g.dev
sudo apt-get install libssl-dev
还要安装php:
sudo apt-get install php5-fpm
后来发现,在新安装的Ubuntu14.04中,不先update直接用:
sudo apt-get install nginx
安装的Nginx的版本是1.4.6,也不是很新。还想要安装更老版本的Nginx(<= 0.8.37)结果总是编译失败,只好放弃。
要开启Nginx对php的支持,去掉配置文件中关于php的注释并重启Nginx即可:
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
#
# # With php5-cgi alone:
# fastcgi_pass 127.0.0.1:9000;
# # With php5-fpm:
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
配置时还遇到许多问题,但只要翻阅错误日志,知道是什么问题,便可对症下药,一一解决。若不知道问题是什么,只看到个400、500,这样子是很难解决问题的。