
PHP_MOD
以模块化的方式运行:将PHP
集成到Apache
中,以同一个进程运行
CGI(公共网关接口)Common Gateway Interface
Apache
在遇到PHP
脚本时,会将其提交给CGI
(php-cgi
)应用程序进行处理,解释之后再将结果返回给Apache
,然后再返回给请求的用户(CGI
是规定web server
传递过来的数据是何种标准格式,简单说就是一个协议)
- 如果请求
/index.html
,那么web server
会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据 - 如果请求的是
/index.php
,根据配置文件,nginx
知道这个不是静态文件,需要去找PHP
解析器来处理,那么他会把这个请求简单处理后交给PHP
解析器
需要时才创建
FASTCGI (多进程的CGI)
这种形式是CGI
的加强版本,CGI
是单进程、多线程的运行方式,程序执行完成之后就会销毁,所以每次都需要加载配置和环境变量(创建-执行)
而FastCGI
则不同,FastCGI
像是一个常驻 (long-live
) 型的CGI
,它可以一直执行着,只要激活后,不会每次都要花费时间去创建新的进程 。FastCGI
进程管理器自身初始化,启动多个CGI
解释器进程 (在任务管理器中可见多个php-cgi
)并等待来自web server
的连接
- 首先,
Fastcgi
会先启一个master
,解析配置文件,初始化执行环境 - 然后再启动多个
worker
- 当请求过来时,
master
会传递给一个worker
,然后立即可以接受下一个请求。这样就避免了重复的劳动,效率提高了 - 而且当
worker
不够用时,master
可以根据配置预先启动几个worker
等着,同时如果发现空闲worker
太多时,也会停掉一些,这样就提高了性能,也节约了资源
提前创建好
Nginx解析漏洞:
nginx
默认以cgi
的方式支持php
的运行,在配置文件中如下配置:
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;
}
背景说明
PHP
中,经常要获取当前请求的URL
的路径信息,一般可以通过环境变量$_SERVER['PATH_INFO']
获取,而cgi.fix_pathinfo
选项与这个值得获取有关,PATH_INFO
是一个CGI1.1
的标准,经常用来作为传递参数给后端的CGI Server
被很多系统用来优化url
路径格式,比如对于很多框架,下面这个网址
比如:http://shtwo.top/tag/Rev27?c=index&m=search
中
$_SERVER['PATH_INFO'] = '/tag/Rev27'
$_SERVER['QUERY_STRING'] = 'c=index&m=search'
php.ini
中的配置参数cgi.fix_pathinfo=1
,它是用来对设置cgi
模式下为php
是否提供绝对路径信息或PATH_INFO
信息
【若PHP
获取不到PATH_INFO
信息,那些依赖PATH_INFO
进行URL
美化的程序就失效了】
配置参数
location
对请求进行选择的时候会使用URI
环境变量(也就是URL
中的传参以及目录格式)进行选择其中传递到后端
Fastcgi
的关键变量SCRIPT_FILENAME
由nginx
生成的$fastcgi_script_name
决定【
fastcgi_param
指令负责将参数传给FastCGI Server
】而通过分析可以看到
$fastcgi_script_name
是直接由URI
环境变量控制的
- 这里就是产生问题的点:而为了较好的支持
PATH_INFO
的提取,在PHP
的配置选项里存在cgi.fix_pathinfo
选项,其目的是为了从SCRIPT_FILENAME
里取出最终要执行的那个文件的名称
攻击场景
假设存在一个
URL
:http://shtwo.top
我们以如下的方式去访问:
http://shtwo.top/test/test.jpg/test.php
漏洞利用nginx
将会得到:/test.jpg/test.php
参数经过
location
指令,该请求将会交给后端的fastcgi
处理,nginx
为其设置环境变量SCRIPT_FILENAME
,内容为 :/scripts/test.jpg/test.php
后端的
fastcgi
在接受到该选项时,会根据fix_pathinfo
配置决定是否对SCRIPT_FILENAME
进行额外的处理 (cgi_fix_pathinfo = 1
),一般情况下如果不对fix_pathinfo
进行设置将影响使用PATH_INFO
进行路由选择的应用,所以该选项一般配置开启【
cgi.fix_pathinfo=1
用于修复路径,如果当前路径不存在则采用上层路径】php
通过该选项查找的方式是查看文件是否存在,在检查到test.php
不存在后,将采用上层路径,也就是test.jpg
此时分离出来的:
PATH_INFO
:test.php
SCRIPT_INFO
:/script/test.jpg
//判断逻辑 if (script_path_translated && (script_path_translated_len = strlen(script_path_translated)) > 0 && (script_path_translated[script_path_translated_len-1] == '/' ||
最后,由于
test.php
不存在,以/scripts/test.jpg
作为此次请求需要执行的脚本,而nginx
会使用php
解析器来处理这个jpg
文件,攻击者就可以实现让nginx
以php
来解析任何类型的文件
漏洞的本质实际上就是由于fastcgi
和web server
对script
路径级参数的理解不同出现的问题:
Nginx
接收到/test.jpg/test.php
的请求时,其 PATH_INFO
会设置为/test.jpg/test.php
,由于其中请求了php
文件,需要解析,于是直接将该路径 PATH_INFO
传递给CGI
,拼接给其$fastcgi_script_name
,交给CGI
进行解析,CGI
收到后会尝试待解析的文件是否存在,若开启了cgi_fix_pathinfo
选项,则当前路径的文件不存在,会继续尝试上一级文件,而此时若是没有设置security.limit_extensions=.php
限制只能解析.php
的文件,则会将所有后缀的文件都尝试以php
文件进行解析,这也就带来了安全的风险
这是典型的因为跨系统语境不同导致对同一个请求的不同解释导致的漏洞,它的攻击面是带有这种漏洞的nginx
预防方式
cgi_fix_pathinfo=0
不合实际,会影响实际业务- 将
php-fpm.conf
中的security.limit_extensions=.php
(该配置将限制fastcgi
解析文件的格式),此项设置为空的时候才允许fastcgi
将.png
等文件当做代码解析
深入学习
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)