前言:以前看php技术文章时遇到过php-fpm的漏洞文章,感到好奇,就简单的了解了一下,还是有很多疑惑,后来又看到过好几次php-fpm的文章,就去深入的学习了一下,分享给大家,希望能够共同成长
0x01 php-Model方式
(引子)Php-fpm是什么:
在介绍php-fpm之前呢,要先清楚的知道cgi、fastcgi、php-cgi和php-fpm
之间的Model方式(关系)
Model方式是什么:
在了解 CGI 之前,我们先了解一下Web server 传递数据的另外一种方法,PHP Module加载方式,就是把php作为apache的一个子模块运行.当通过web访问php文件时,apache就会调用php5_module来解析php代码
Php5_module 通过sapi将数据传给php解析器解析php代码
(下图及apache和php与sapi关系)

apache每接收一个请求,都会产生一个进程来连接php通过sapi来完成请求,如果一旦用户过多,并发数过多,服务器就会承受不住了。
而且,把mod_php编进apache时,出问题时很难定位是php的问题还是apache的问题
0x02 CGI是什么
CGI的好处就是完全独立于任何服务器,仅仅是做为中间分子。提供接口给apache和php。他们通过cgi搭线来完成数据传递。这样做的好处了尽量减少2个的关联,使他们2变得更独立。
CGI是什么
早期的web服务器,只能处理用户请求的静态资源如html,并将存储在服务器端的静态资源返回给浏览器,由于web服务器不能处理动态脚本(php),无法实现数据互通,为了解决此问题出现了CGI(Common Gateway Interface)全称是”通用网关接口”,其实就是web服务器(中间件)与动态语言进行”交流”的协议(约定)

Web服务器会传url,查询字符串,post数据,HTTP headers给php解析器,但是cgi在高并发时效率会很低.因为web服务器遇到动态请求时,服务器会fork一个新的进程启动CGI程序,解析动态脚本语言,将结果返回给web服务器,web服务器返回给客户端,处理完成后fork的进程关闭,之后用户每次请求动态脚本语言都是这样一个复杂的过程,其效率可想而知
0x03 Fast-cgi,Php-cgi,Php-fpm
Fastcgi是什么:
为了解决web服务器每接收一个动态请求都fork一个进程,结束kill掉这个进程的问题,便出现了CGI的改良版--Fast-CGI(Fast Common GatewayInterface/FastCGI)快速通用网关接口
Fast-CGI每次处理完请求后,不会kill掉这个进程,而是保留这个进程,从而使服务器可以同时处理更多的网页请求。这样就会大大的提高效率。
PHP-CGI:是 PHP (Web Application)对 Web Server 提供的 CGI 协议的接口程序。 同时是PHP自带的Fast-CGI管理器,性能差,很麻烦不人性化,PHP-FPM的出现优化了PHP-CGI
PHP-FPM:是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能一些任务管理.PHP-FPM是Fast-cgi的进程管理器,是FastCGI的一个具体实现,监听9000端口
包含了master和worker进程,其中master进程负责与web服务器中间件进行通信,接收用户请求,此请求服务器按照Fast-CGI转换,交给worker进程处理,执行php代码


0x04 Fast-cgi协议分析
Fastcgi Record(记录)ps 产生的一条数据请求
类似http协议实现浏览器与服务器中间件数据通信,浏览器将http头与http体发送给服务器,服务器按照规则将数据包解码,拿到用户数据
Fastcgi 其实是一个通信协议,和HTTP协议一样,是实现服务器中间件和某种后端语言的通信,Fastcgi 协议由多个 Record 组成,Record 也包含Header和 Body,服务器中间件将这二者按照 Fastcgi 的规则封装好发送给后端语言,后端语言拿到具体数据,按照 Fastcgi 协议封装好后返回给服务器中间件。
消息头:
fastcgi Record 的头固定8个字节,Body 是由头中的 content Length 指定,请求头是公共的部分,不管type类型是什么请求头格式都是相同的,请求体的部分随着type的改变而改变其结构如下:
type defstruct{
/* Header 消息头信息 */
unsigned char version;// 用于表示 FastCGI 协议版本号
unsigned char type;// 用于标识 FastCGI 消息的类型, 即用于指定处理这个消息的方法
unsigned char requestIdB1;// 用ID值标识出当前所属的 FastCGI 请求
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 类型的变量组成,每个变量一个字节。其中,requestId区分不同请求的id,以避免多个请求之间的影响;contentLength 表示 Body 的大小。可见,一个 Fastcgi Record 结构最大支持的 Body 大小是2^16,也就是 65536 字节。
后端语言解析了 Fastcgi 头以后,拿到 contentLength,然后再在请求的TCP 流里读取大小等于 contentLength 的数据,这就是 Body 体。
Fastcgi Type ps(头类型)
刚才我们介绍了 Fastcgi 协议中Record部分中请求消息头中各个结构的含义,其中第二个字节为 type,我们type 就是指定该 Record 的作用。因为 Fastcgi 中一个 Record 的大小是有限的,作用也是单一的,所以我们需要在一个TCP流里传输多个 Record,通过 type 来标志每个 Record 的作用,并用 requestId 来标识同一次请求的id。也就是说,每次请求,会有多个 Record,他们的 requestId 是相同的。
下面给出一个表格,列出最主要的几种 type:

这里着重关注type=4的请求体与后端语言解析的内容,未授权和远程代码执行是php-fpm传递环境参数可控导致的,但也得了解其的type对应的消息体
欢迎大家关注黑客街安全团队
