目录
前置知识
Fastcgi简介
想要了解php-fpm就先要知道Fastcgi,Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。
record结构
cpp
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的类型,因为fastcgi一个record的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个record。通过`type`来标志每个record的作用,用`requestId`作为同一次请求的id。
unsigned char requestIdB1; // 本次record对应的请求id
unsigned char requestIdB0;
unsigned char contentLengthB1; // body体的大小
unsigned char contentLengthB0;
unsigned char paddingLength; // 额外块大小
unsigned char reserved;
/* Body */
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;
语言端解析了fastcgi头以后,拿到contentLength
,然后再在TCP流里读取大小等于contentLength
的数据,这就是body体。
环境变量
当后端语言收到type为4的record后,就会把这个record的body按照对应的结构解析成key-value对,这就是环境变量。
PHP-FPM简介
FPM其实是一个fastcgi协议解析器,Nginx等服务器中间件将用户请求按照fastcgi的规则打包好通过TCP传给谁?其实就是传给FPM。FPM按照fastcgi的协议将TCP流解析成真正的数据。
例子
用户访问http://127.0.0.1/index.php?a=1&b=2
,如果web目录是/var/www/html
,那么Nginx会将这个请求变成如下key-value对,这就是环境变量,然后这个环境变量的作用不仅是填充$_SERVER
数组,也是告诉fpm:"我要执行哪个PHP文件"。
cpp
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
}
Nginx(IIS7)解析漏洞
下面以例子来解释一下这个漏洞,在volhub搭建好环境,在搭建好的环境下有一个图片码
然后在docker映射的端口访问这个图片马的路径后面再加一个不存在的路径如下,aaaa.php不存在
cpp
http://192.168.244.152:9090/uploadfiles/nginx.png/aaaa.php
然后在aaaa.php不存在的情况下图片马被以php方式执行了
漏洞原因
在php.ini文件中开启起了cgi.fix_pathinifo这个配置选项,这参数配置了以后可以自动修复路径。当当前文件找不到以后,php就会向上找文件,直到找到文件,在上面的例子中就是aaaa.php找不到后就去找nginx.png文件,找到了nginx.png后,以php方式执行。
解决方法
不开启cgi.fix_pathinifo这个配置选项
在php-fpm自配置文件路径中有一个安全字段security.limit_extensions这个字段可以规定php可以解析的文件,将这个字段设置成.php后没有办法解析其他的了
在php-fpm中解析的过程
用户请求http://192.168.244.152:9090/uploadfiles/nginx.png/aaaa.php时,nginx将会发送如下环境变量到fpm里:
cpp
{
...
'SCRIPT_FILENAME': '/var/www/html/uploadfiles/nginx.png/aaaa.php',
'SCRIPT_NAME': 'aaaa.php',
'REQUEST_URI': '/uploadfiles/nginx.png/aaaa.php',
'DOCUMENT_ROOT': '/var/www/html',
...
}
php-fpm未授权访问漏洞漏洞原因
(前置知识完了下面来看一下正主)
一些开发者为了方便将自己设备的9000端口开放在公网上,而且PHP-FPM默认监听9000端口,然后其他人就可以访问他PHP-FPM。
然后攻击者可以访问其他人的PHP-FPM后,攻击者可以构造fastcgi的数据,然后让对方来处理。
这时'SCRIPT_FILENAME'这个环境变量的值就很重要了,因为fpm时根据这个值来执行PHP,文件不存在就返回404
在fpm某个版本之前,我们可以将SCRIPT_FILENAME
的值指定为任意后缀文件,比如/etc/passwd
;但是后来就加了security.limit_extensions这个字段。就只可以解析php的文件了。
payload构造
这里使用脚本,在没有进行任何配置的时候脚本运行失败,这是因为security.limit_extensions这个字段被设置了只允许php执行,然后我们可以执行对方服务器的任意php代码,对方的服务器肯定会有php代码,因为在装php-fpm的时候就会带有php代码,但是好像意义不大,但是不急接着往下看
php配置文件中中有两个字段auto_prepend_file
和auto_append_file
。auto_prepend_file
在执行目标文件之前,先包含auto_prepend_file
中指定的文件;auto_append_file
是告诉PHP,在执行完成目标文件后,包含auto_append_file
指向的文件。
若是我们可以控制auto_prepend_file这个字段,且将这个字段者只为php://input,那我们在执行目标php代码前就可以执行POST的内容(注:
还需要开启远程文件包含选项allow_url_include)
那么我们如何设置auto_prepend_file,这就又要看php-fpm的环境变量了。在php-fpm中又有两个环境变量就是用来设置php配置项的,PHP_VALUE可以设置模式
好准备工作做完了,现在开始使用脚本构造payload
构造后的payload ,那两个字段已经被设置了