也谈Nginx的CGI PATH INFO问题


这两天在配置一个用MVC编写的PHP站点程序时候遇到了点小问题,就是那个PATH INFO的问题。实际上,这个概念前段时间被炒的很热乎,不过那时候自己没有太关注Ngixn,也就没太在意,现在自己碰到了,就留意了一下。
首先是看到这个老兄的文章 http://hily.me/blog/?p=1083 不过他里面说的在我的站点上没有重现,为啥呢,因为我之前较早的nginx配置方案里面,做了文件系统检查(当初只是为了节约资源让Nginx来判断文件存在否,这样不把不存在的文件交给FastCGI来解析,能减轻FastCGI的负担,没想到意外的阻止了cgi.fix_pathinfo漏洞,真是一个惊喜! :-x ):

#不存在的文件返回404
if (!-e $request_filename) {
return 404;
}

这样,任何基于Path Info的请求都被文件系统检查阻断了,因为Path Info的是虚拟URL,无法对应到文件系统上面真实的文件。下面示例代码可以检测这种状态。
新建一个文件,叫go.png,内部粘贴如下代码

上传到服务器上,访问go.png,用IE访问会显示出源代码,用FF访问就会提示图像错误。
我们再在后面加一点东西如何
访问go.png/a.php
提示404错误
因为我做了文件系统检查,go.png/a.php这个文件不存在。
那么,我把那个文件系统检查打掉怎么样呢
立马就显原形了

到这里,nginx的Path Info漏洞到此全部重现。

需要注意到是,那个方案里面没有开启Nginx对 Path Info的完整支持,之所以访问go.png/a.php能执行go.png里面的php代码是因为nginx是通过文件后缀名来匹配文件的,当go.png/a.php被nginx捕获到后交给FastCGI Server来处理,对于这些不存在的路径,PHP 会检查路径中存在的文件,并将多余的部分当作 PATH_INFO。这个就是PHP Fix Path Info的来历。如果访问go.png/a这样的正常的形式,由于nginx捕获不到php的文件后缀,自然也不会交给FastCGI Server来处理,也就不会有PHP的cgi.fix_pathinfo来掺和,自然就不会有这个漏洞了,nginx直接返回404。

在保持当前文件不变的情况下,我单方面把php的cgi.fix_pathinfo关掉,继续测试
访问go.png/a.php。不是运行go.png的代码了,提示No input file specified.

上面说到我的那个配置方案没有完成完整的ngixn Path Info支持,这样的话,很多MVC开发的站点程序使用PathInfo来获取信息的话就是致命的打击,怎么办呢?
从nginx ≥ 0.7.31 开始,有了一个fastcgi_split_path_info ,这玩意可以完成完整的fastCGI的Path Info支持。

#FastCGI配置 开启Path_info支持
location ~* ^(.+\.php)(.*)$ {
fastcgi_pass unix:/var/run/www.sock;

fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $rootdir/$fastcgi_path_info;

fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $rootdir/$fastcgi_script_name;
include fastcgi_params;
}

首先我们打开cgi.fix_pathinfo,然后把go.png换成pathinfo.php,内容不变,上传
访问pathinfo.php,输出

访问pathinfo.php/test,输出

从输出可以看到,Path Info已经完整的得到了nginx的支持,Path Info里面的参数得到了妥善处理。

从中我们看到了nginx对Path Info的良好支持,也看到了使用Fix Path Info不当带来的后果,使用上面的Nginx配置必须把cgi.fix_pathinfo打开,否则根本无法工作,那马怎么兼顾安全又使用这个 Path Info特性呢?

由于大多是基于MVC开发的程序,不如wordpress等主要功能都只有一个入口,index.php,我们也可以通过rewrite来实现,但是那么多的站点每个都要写,实在是谈不上效率,其实要是能让php5-fpm只解析php后缀的文件,问题也解决了,但是我也没找到方案,所以只要让nginx来完成这个过程。

if ($request_filename ~* (.*)\.php) {
set $php_url $1;
}
if (!-e $php_url.php) {
return 404;
}

文件系统检查移动到fastcgi_params的头部,并且只对php后缀做检查,这样就可以安全的开启cgi.fix_pathinfo啦! :oops:

Author Info :
  • From:也谈Nginx的CGI PATH INFO问题
  • URL:http://blog.ihipop.info/2011/02/2118.html
  • Please Reserve This Link,Thanks!
  • 《也谈Nginx的CGI PATH INFO问题》有4个想法

    1. if ($request_filename ~* (.*)\.php) {
      set $php_url $1;
      }
      if (!-f $php_url.php) {
      return 404;
      }

      改成这样更安全,如果是.php文件夹,那样也很危险

    发表评论

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