前言
没有学习java的第n天,寒假学的基本忘完
被屁事绑架的第n天,好久没有安静的学习了
请ki10Moc谨记,就算周遭只有一个人往前走,也要越走越快,不要停;就算只有一个人想要认真学习技术,也要越学越勇;不要理会这个世界
其实这是个很“古老”的话题了
CGI
CGI( Common Gateway Interface , 通用网关接口 )的出现让 Web 从静态变为动态 .
当 WebServer 认为这是一个 CGI 请求时 , 会调用相关的 CGI 程序 , 并封装环境变量和标准输入等数据 , 然后传输给 CGI 程序 . CGI 程序处理完毕后会生成 HTML 页面 , 然后再通过标准输出将页面返回给 WebServer
所以对于用户想服务器发起的请求,服务器会解析并识别像php,jsp这类语言类别,并调取相关的处理器来进行处理。
丑图流程如下:

但这会产生新的问题,CGI程序采用fork and execution
处理事务,那每次的请求都是一次独立的事务,就需要建立新的CGI程序来进行处理,这样在面对高并发的事务下就显得捉襟见肘,可能还会引发安全问题。
FastCGI
FastCGI实际上是一个通信协议,与HTTP协议一样,是进行数据交换的一个通道。

和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:
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指定,其保留作用,不需要是其长度可以设置为0。
version : 显示版本信息 , 如果是 WebServer 发送给 PHP-FPM 的消息 , 请求头中仅需要将其置 " 0 " 即可
Type : 每次发送请求的类型 , 这个比较复杂后面再说
RequestId : 占两个字节 , 这是一个唯一的标识Id , 以避免 PHP-FPM 同时处理多个请求时的影响
ContentLength : 占两个字节 , 用来表示此 Record Body 中数据的长度 , PHP-FPM 拿到该字段后 , 会从 TCP 数据流中读取相同大小的数据 , 作为 Record Body .
PaddingLength : 额外填充长度 , 为了提高处理请求的能力 , 每个请求的大小都必须为 8 的倍数 . 该长度表示在请求尾部填充空白的长度 .
Reserved : 保留字段
contentData : Record Body 数据 , 其支持的最大 Body 大小为 65536 字节 .
paddingData : 额外填充的空白数据

从中可以明确看出 : WebServer 和后端语言通信时,发送的第一个数据包是 Type 为 1 的Record , 然后两者建立连接并互相通信 , 陆续发送 Type 为 4 , 5 , 6 , 7 的 Record,结束通信时发送 Type 为 2 , 3 的 Record
其中 , Type 为 4 的 Record 是最值得关注的 , 因为环境参数在 PHP-FPM 中有着至关重要的作用,就要结合后面的漏洞利用了。
关于流程,偷个图

PHP-FPM
如果你是web狗或是学开发的接触过php的肯定都知道并有一定了解
PHP-FPM全称是PHP FastCGI Process Manager,也就是PHP FastCGI进程管理器
我们先来看一个Dem0
SetHandler application/x-httpd-php
如果你有学习过.htaccess文件上传的绕过方面的知识
看到这个会很熟悉,这里其实就是apache配置文件中的一句话,意思是调用php语言的相关处理器,也可以叫解释器。
FastCGI为了提高CGI程序的性能,采用进程池的凡是解决每次fork新的CGI进程的开销
且通过FastCGI进程管理器统一管理CGI进程
简单来说:以前一个CGI程序,服务器就要开辟一个进程和内存,但是现在一个CGI程序下发出去几个子线程,由这些子线程对请求的统一处理后返回给CGI程序,这样就省去了一些资源。
FastCGI Type
关于fastcgi一个record中第二个字节type,是指定该record的作用,因为fastcgi中一个record的大小是有限的,其作用也是单一的,所以需要在TCP流中提供多个record,因为type会指定record的作用,requestId作为请求的id。
所以在每次请求体重会有多个record,而他们的id也是相同的
PHP安装模式
均可在服务器的phpinfo下查看Server API
CGI模式安装: CGI/FastCGI
FastCGI模式安装:FPM/FastCGI
Module模式安装:Apache2.0Handler
漏洞利用
前面说到PHP-FPM每次都是按照FastCGI协议的格式打包好TCP数据流解析成数据交给进程池中的子进程中对应的后端语言解释器处理。
若用户访问http://127.0.0.1/index.php?a=1&b=2,在Nginx中该请求为:
{'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配置文件如下

如果有充足的时间,还是希望读者能自己去阅读一下源码,对了解Nginx和协议交互有很大的帮助。并且前面的内容写的都很简略或者说非常粗糙,因为这些东西看过源码后可以了解到的。
这个数组其实就是PHP中的 $_SERVER
数组的一部分 , 同时也是PHP里的环境变量 . 但环境变量的作用不仅是填充 $_SERVER
数组 , 也是告诉 PHP-FPM
用户要执行哪个PHP文件 .
PHP-FPM
会将 SCRIPT_FILENAME
指向这个PHP文件,也就是 /var/www/html/index.php
. 然后分配给进程池中的 PHP 解析器处理 .

如果 SCRIPT_FILENAME
是一个不存在的文件 , 按理说 PHP-FPM 找不到目标文件 , 然后 WebServer 将会返回 HTTP 404 错误 , 最后结束此次请求 . 整个过程没有任何问题 .
但是如果若管理员开启了 cgi.fix_pathinfo
选项 , 则会产生解析漏洞 . PHP为了支持 Path Info 模式而创造了 cgi.fix_pathinfo
选项 , 这个选项被打开的情况下 , PHP-FPM
会判断 SCRIPT_FILENAME
是否存在 , 如果不存在则去掉最后一个 ” / ” 及以后的所有内容 , 再次判断文件是否存在 . 往次循环 , 直到文件存在 , 然后去解析这个文件
这就是经典的Nginx / IIS 解析漏洞