这两天在配置一个用MVC编写的PHP站点程序时候遇到了点小问题,就是那个PATH INFO的问题。实际上,这个概念前段时间被炒的很热乎,不过那时候自己没有太关注Ngixn,也就没太在意,现在自己碰到了,就留意了一下。
首先是看到这个老兄的文章 http://hily.me/blog/?p=1083 不过他里面说的在我的站点上没有重现,为啥呢,因为我之前较早的nginx配置方案里面,做了文件系统检查(当初只是为了节约资源让Nginx来判断文件存在否,这样不把不存在的文件交给FastCGI来解析,能减轻FastCGI的负担,没想到意外的阻止了cgi.fix_pathinfo漏洞,真是一个惊喜! ):
#不存在的文件返回404
if (!-e $request_filename) {
return 404;
}
这样,任何基于Path Info的请求都被文件系统检查阻断了,因为Path Info的是虚拟URL,无法对应到文件系统上面真实的文件。下面示例代码可以检测这种状态。
新建一个文件,叫go.png,内部粘贴如下代码
[php]<?php
echo "<pre>";
print_r($_SERVER);
echo "</pre>";
?>[/php]
上传到服务器上,访问go.png,用IE访问会显示出源代码,用FF访问就会提示图像错误。
我们再在后面加一点东西如何
访问go.png/a.php
提示404错误
因为我做了文件系统检查,go.png/a.php这个文件不存在。
那么,我把那个文件系统检查打掉怎么样呢
立马就显原形了
[php]Array
(
[USER] => www-data
[HOME] => /var/www
[FCGI_ROLE] => RESPONDER
[SCRIPT_FILENAME] => /var/ooxx.com/go.png
[QUERY_STRING] =>
[REQUEST_METHOD] => GET
[CONTENT_TYPE] =>
[CONTENT_LENGTH] =>
[SCRIPT_NAME] => /go.png/a.php
[REQUEST_URI] => /go.png/a.php
[DOCUMENT_URI] => /go.png/a.php
[DOCUMENT_ROOT] => /var/ooxx.com
[SERVER_PROTOCOL] => HTTP/1.1
[GATEWAY_INTERFACE] => CGI/1.1
[SERVER_SOFTWARE] => nginx/0.8.54
[REMOTE_ADDR] => 218.93.123.29
[REMOTE_PORT] => 37805
[SERVER_ADDR] => 216.24.199.154
[SERVER_PORT] => 443
[SERVER_NAME] => ooxx.com
[REDIRECT_STATUS] => 200
[HTTPS] => on
[HTTP_HOST] => ooxx.com
[HTTP_USER_AGENT] => Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
[HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
[HTTP_ACCEPT_LANGUAGE] => zh-cn,zh;q=0.5
[HTTP_ACCEPT_ENCODING] => gzip,deflate
[HTTP_ACCEPT_CHARSET] => GB2312,utf-8;q=0.7,*;q=0.7
[HTTP_KEEP_ALIVE] => 115
[HTTP_CONNECTION] => keep-alive
[HTTP_CACHE_CONTROL] => max-age=0
[ORIG_SCRIPT_FILENAME] => /var/ooxx.com/go.png/a.php
[PATH_TRANSLATED] => /var/ooxx.com
[PHP_SELF] => /go.png/a.php
[REQUEST_TIME] => 1297826490
)[/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,输出
[php]Array
(
[USER] => www-data
[HOME] => /var/www
[FCGI_ROLE] => RESPONDER
[PATH_INFO] =>
[PATH_TRANSLATED] => /var/ooxx.com/
[SCRIPT_FILENAME] => /var/ooxx.com/pathinfo.php
[QUERY_STRING] =>
[REQUEST_METHOD] => GET
[CONTENT_TYPE] =>
[CONTENT_LENGTH] =>
[SCRIPT_NAME] => /pathinfo.php
[REQUEST_URI] => /pathinfo.php
[DOCUMENT_URI] => /pathinfo.php
[DOCUMENT_ROOT] => /var/ooxx.com
[SERVER_PROTOCOL] => HTTP/1.1
[GATEWAY_INTERFACE] => CGI/1.1
[SERVER_SOFTWARE] => nginx/0.8.54
[REMOTE_ADDR] => 218.93.123.29
[REMOTE_PORT] => 38012
[SERVER_ADDR] => 216.24.199.154
[SERVER_PORT] => 443
[SERVER_NAME] => ooxx.com
[REDIRECT_STATUS] => 200
[HTTPS] => on
[HTTP_HOST] => ooxx.com
[HTTP_USER_AGENT] => Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
[HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
[HTTP_ACCEPT_LANGUAGE] => zh-cn,zh;q=0.5
[HTTP_ACCEPT_ENCODING] => gzip,deflate
[HTTP_ACCEPT_CHARSET] => GB2312,utf-8;q=0.7,*;q=0.7
[HTTP_KEEP_ALIVE] => 115
[HTTP_CONNECTION] => keep-alive
[PHP_SELF] => /pathinfo.php
[REQUEST_TIME] => 1297832487
)[/php]
访问pathinfo.php/test,输出
[php]Array
(
[USER] => www-data
[HOME] => /var/www
[FCGI_ROLE] => RESPONDER
[PATH_INFO] => /test
[PATH_TRANSLATED] => /var/ooxx.com/test
[SCRIPT_FILENAME] => /var/ooxx.com/pathinfo.php
[QUERY_STRING] =>
[REQUEST_METHOD] => GET
[CONTENT_TYPE] =>
[CONTENT_LENGTH] =>
[SCRIPT_NAME] => /pathinfo.php
[REQUEST_URI] => /pathinfo.php/test
[DOCUMENT_URI] => /pathinfo.php/test
[DOCUMENT_ROOT] => /var/ooxx.com
[SERVER_PROTOCOL] => HTTP/1.1
[GATEWAY_INTERFACE] => CGI/1.1
[SERVER_SOFTWARE] => nginx/0.8.54
[REMOTE_ADDR] => 218.93.123.29
[REMOTE_PORT] => 38014
[SERVER_ADDR] => 216.24.199.154
[SERVER_PORT] => 443
[SERVER_NAME] => ooxx.com
[REDIRECT_STATUS] => 200
[HTTPS] => on
[HTTP_HOST] => ooxx.com
[HTTP_USER_AGENT] => Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
[HTTP_ACCEPT] => text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
[HTTP_ACCEPT_LANGUAGE] => zh-cn,zh;q=0.5
[HTTP_ACCEPT_ENCODING] => gzip,deflate
[HTTP_ACCEPT_CHARSET] => GB2312,utf-8;q=0.7,*;q=0.7
[HTTP_KEEP_ALIVE] => 115
[HTTP_CONNECTION] => keep-alive
[PHP_SELF] => /pathinfo.php/test
[REQUEST_TIME] => 1297832734
)[/php]
从输出可以看到,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啦!
@hdr
为什么呢?
为啥要用 -f而不用 -e呢?
if ($request_filename ~* (.*)\.php) {
set $php_url $1;
}
if (!-f $php_url.php) {
return 404;
}
改成这样更安全,如果是.php文件夹,那样也很危险