上一篇FPM提到了FastCGI协议,在这里粗略的整理下
协议分析
请求头
web服务器和FastCGI程序每传输一个消息,首先会传输一个8字节固定长度的消息头,这个消息头记录了随后要传输的这个消息的 类型,长度等等属性,消息头的结构体如下(go语言):
|
|
Type字段用来说明每次所发送消息的类型,其具体值可以为如下
type值 | 具体含义 |
---|---|
1 | 在与php-fpm建立连接之后发送的第一个消息中的type值就得为1,用来表明此消息为请求开始的第一个消息 |
2 | 异常断开与php-fpm的交互 |
3 | 在与php-fpm交互中所发的最后一个消息中type值为此,以表明交互的正常结束 |
4 | 在交互过程中给php-fpm传递环境参数时,将type设为此,以表明消息中包含的数据为某个name-value对 |
5 | web服务器将从浏览器接收到的POST请求数据(表单提交等)以消息的形式发给php-fpm,这种消息的type就得设为5 |
6 | php-fpm给web服务器回的正常响应消息的type就设为6 |
7 | php-fpm给web服务器回的错误响应设为7 |
|
|
PaddingLength:填充长度的值,为了提高处理消息的能力,我们的每个消息大小都必须为8的倍数,此长度表示在消息的尾部填充的长度
FastCGI采用大端序,所以如果收到消息头信息为0x01010FFF00080000
说明verison=1 type=1 id=4095 content length=8 padding length = 0
请求体
对于 FCGI_BEGIN_REQUEST 和 FCGI_END_REQUEST 消息类型,fastcgi协议分别定义了一个结构体如下,而对于其他类型的消息体,没有专门结构体与之对应,消息体就是普通的二进制数据。
|
|
role字段占2个字节,用来说明我们对php-fpm发起请求时,我们想让php-fpm为我们扮演什么角色,其常见的3个取值如下:
role值 | 具体含义 | 名字 |
---|---|---|
1 | 最常用的值,php-fpm接受我们的http所关联的信息,并产生个响应 | FCGI_RESPONDER |
2 | php-fpm会对我们的请求进行认证,认证通过的其会返回响应,认证不通过则关闭请求 | FCGI_AUTHORIZER |
3 | 过滤请求中的额外数据流,并产生过滤后的http响应 | FCGI_FILTER |
flags 是一个8位掩码, web服务器可以利用该掩码告知FastCGI程序在处理完一个请求后是否关闭socket连接 (最初协议的设计者可能还预留了该掩码的其他作用,只是目前只有这一个作用)
flags & FCGI_KEEP_CONN 的值为1,则FastCGI程序请求结束不关闭连接,为0 则关键连接
其中 FCGI_KEEP_CONN 是一个宏,定义如下:
|
|
|
|
AppStatus是应用级别的状态码。每个角色记录其对appStatus的使用情况
ProtocolStatus 是协议级别的状态码,可能的protocolStatus值是:
ProtocolStatus值 | 具体含义 | 名字 |
---|---|---|
1 | 请求的正常结束,典型的应该都是该个值 | FCGI_REQUEST_COMPLETE |
2 | 拒绝新的请求。当Web服务器通过一个连接将并发请求发送到每个连接一次处理一个请求的应用程序时,会发生这种情况 | FCGI_CANT_MPX_CONN |
3 | 拒绝新的请求。当应用程序耗尽某些资源(例如数据库连接)时会发生这种情况 | FCGI_OVERLOADED |
4 | 拒绝新的请求。当Web服务器指定了应用程序未知的角色时,会发生这种情况 | FCGI_UNKNOWN_ROLE |
还有种要说的就是当type=FCGI_PARAMS
,传递环境参数时FCGI_PARAMS
以键值对的方式传送,键和值之间没有’=‘,每个键值对之前会分别用1或4个字节来标识键和值的长度 例如:x0B\x02SERVER_PORT80\x0B\x0ESERVER_ADDR199.170.183.42
上面的长度是用十六进制表示的 \x0B
= 11 正好为SERVER_PORT
的长度, \x02
= 2 为80的长度
消息发送流程
1、web服务器向FastCGI程序发送一个8字节 type=FCGI_BEGIN_REQUEST
的消息头和一个8字节 FCGI_BeginRequestBody
结构的消息体,标志一个新请求的开始
2、web服务器向FastCGI程序发送一个8字节 type=FCGI_PARAMS
的消息头 和一个消息头中指定长度的FCGI_PARAMS
类型消息体
3、根据FCGI_PARAMS消息的长度可能重复步骤 2 多次,最终发送一个8字节 type=FCGI_PARAMS
并且 contentLength
高位和低位都为0的消息头标志FCGI_PARAMS
消息发送结束
4、以和步骤2、3相同的方式 发送FCGI_STDIN
消息
5、FastCGI程序处理完请求后,以和步骤2、3相同的方式,发送FCGI_STDOUT
消息和FCGI_STDERR
消息返回给服务器
6、FastCGI程序发送一个type=FCGI_END_REQUEST
的消息头和一个8字节FCGI_EndRequestBody
结构的消息体,标志此次请求结束
FastCGI远程利用
前提
php >= 5.3.3
php-fpm监听9000端口
9000端口开放在外网或存在ssrf漏洞
利用
伪装成webserver,与php-fpm通过fastcgi进行交互,执行构造好的恶意代码
因为能够通过设置FASTCGI_PARAMS
,利用PHP_ADMIN_VALUE
和PHP_VALUE
去动态修改php的设置
1 2 3 | env["REQUEST_METHOD"] = "POST" env["PHP_VALUE"] = "auto_prepend_file = php://input" env["PHP_ADMIN_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off" |
再通过FCGI_STDIN
传递要执行的php代码,利用执行php://input
,然后在POST的内容中写入我们的php代码,这样就可以直接任意代码执行了
ssrf则配合gopher协议食用
exp
这里附上原作者的exp https://github.com/wofeiwo/webcgi-exploits/tree/master/php/Fastcgi
(这远程利用文章原作者原来在乌云发的
参考
https://blog.csdn.net/zhang197093/article/details/78914509 Author:zhang197093
https://blog.csdn.net/Shreck66/article/details/50355729 Author:Shreck66
https://fastcgi-archives.github.io/FastCGI_Specification.html
https://github.com/wofeiwo/webcgi-exploits/blob/master/php/Fastcgi/php-fastcgi-remote-exploit.md Author:wofeiwo