文件解析漏洞总结-Nginx

与Apache相比,Nginx算是后起之秀,但大有赶超之势。百度一番,又谷歌一番,最终只找到了三个已公开的、关于Nginx的解析漏洞,记录如下。

Nginx中php配置错误导致的解析漏洞

利用方式:

  /test.jpg/test.php

测试:

首先准备文件test.jpg,内容为:

  <?php phpinfo() ?>

在浏览器中访问 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被成功执行,如下图所示:

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页面和上图不同:

不存在的php文件

查看错误日志,找到了:

  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做出的判断。

%00截断

影响范围:

  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

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 ”,注意文件名的最后一个字符是空格,文件内容为:

  <?php phpinfo() ?>

用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

为验证漏洞,需要安装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,这样子是很难解决问题的。

One Reply to “文件解析漏洞总结-Nginx”

发表回复

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

11 + 17 =