初识Fastcgi

0x00写在前面

看了p神的博客和其他大佬的博客后,发现自己对通讯这边还是不太懂.
希望摘抄下他们的博客,能够加深理解吧orz

0x01简介

搭过php相关环境的同学应该对fastcgi不陌生,那么fastcgi究竟是什么东西,为什么nginx可以通过fastcgi来对接php?
Fastcgi其实是一个通信协议,和HTTP协议一样,都是进行数据交换的一个通道。
HTTP协议是浏览器和服务器中间件进行数据交换的协议,浏览器将HTTP头和HTTP体用某个规则组装成数据包,以TCP的方式发送到服务器中间件,服务器中间件按照规则将数据包解码,并按要求拿到用户需要的数据,再以HTTP协议的规则打包返回给服务器。

类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端进行数据交换的协议。Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端,语言后端解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件。

和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的类型
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;

头由8个uchar类型的变量组成,每个变量1字节。其中,requestId占两个字节,一个唯一的标志id,以避免多个请求之间的影响;contentLength占两个字节,表示body的大小。

语言端解析了fastcgi头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体。

Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用。不需要该Padding的时候,将其长度设置为0即可。

可见,一个fastcgi record结构最大支持的body大小是2^16,也就是65536字节。

0x02PHP的连接方式

apache2-module

把php当作apache的一个模块,实际上php就相当于apache的一个dll或者so文件.

CGI模式

此时php是一个独立的进程比如php-cgi.exe,web服务器也是一个独立的进程比如apache.exe,当web服务器监听到http请求时,会去调用php-cgi进程,他们之间通过cgi协议通信.CGI的缺点也很明显,每次客户端请i去都需要建立和销毁进程.

FastCGI模式

fastcgi本身时一个协议,在cgi协议上进行优化.fastcgi模式是fastcgi进程自己管理自己的cgi进程,而不再是apache去主动调用php-cgi,而fastcgi进程又提供了很多辅助功能比如内存管理,垃圾处理,保障了cgi的高效性,并且CGI此时是常驻在内存中,不会每次请求重新启动

PHP-fpm

fastcgi本身是一个协议,那么就需要有一个程序去实现这个协议,php-fpm就是实现和管理fastcgi协议的进程,fastcgi模式的内存管理等功能,都是由php-fpm进程所实现的

0x03判断连接方式

如果接触不到服务器文件,我们可以通过phpinfo页面的Server API来判断

php-fpm的模式

php-fpm与nginx之间的通信有两种模式,其一为TCP模式,其二是unix套接字模式

TCP模式

TCP模式即是php-fpm进程会监听本机上的一个端口(默认9000),然后nginx会把客户端数据通过fastcgi协议传给9000端口,php-fpm拿到数据后会调用cgi进程解析

Unix Socket

socket,它是unix系统进程间通信(IPC)的一种被广泛采用方式,以文件(一般是.sock)作为socket的唯一标识(描述符),需要通信的两个进程引用同一个socket描述符文件就可以建立通道进行通信了。此通信方式的性能会优于TCP

0x04漏洞产生

php-fpm默认监听端口9000,如果其暴露在公网,那我们就可以构造fastcgi协议,和fpm进行通信.

任意代码执行

理论上是不可以执行任意代码的.
但php.ini中有两个配置项auto_prepend_fileauto_append_file.
auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件;auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件。
假设我们设置auto_prepend_filephp://input,那么就等于在执行任何php文件时都会包含POST内容.(前提是allow_url_include开启)
至于怎么设置auto_prepend_file.php-fpm有两个环境变量,PHP_VALUEPHP_ADMIN_VALUE.这两个变量是用来设置php配置项的吗,PHP_VALUE可以设置模式为PHP_INI_USERPHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。
所以,只要传入如此的环境变量即可执行任意代码了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
'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'
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}

0x05exp

P神的payload,兼容python2,python3
https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75

参考博客

Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
PHP 连接方式&攻击PHP-FPM&*CTF echohub WP