这篇文章介绍fastcgi的攻击方式。
ssrf之攻击fastcgi
1.了解CGI、FastCGI、PHP-FPM
我们知道,在网站架构中,Web Server(如Nginx)只是内容的分发者
当客户端请求的是index.php,根据配置文件Web Server辨别不是静态文件,此时就需要去找 PHP解析器来处理
当Web Server收到 index.php 这个请求后,会启动对应的CGI 程序,也就是PHP解析器
接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规范的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。这就是一个完整的动态PHP Web访问流程
这其中,引出如下概念:
1 | CGI:是 Web Server 与 Web Application 之间数据交换的一种协议 |
PHP默认提供了很多种SAPI(服务器端应用编程端口),常见的提供给apache和nginx的php5_module、CGI、FastCGI,给IIS的ISAPI,以及Shell的CLI
经过不断的技术升级,目前搭建高性能的PHP Web服务器,最佳的方式是Apache/Nginx + FastCGI + **PHP-FPM(PHP-CGI)**方式
FastCGI工作原理
1 | 1.Web 服务器启动时载入FastCGI进程管理器(PHP-CGI或者PHP-FPM) |
由此知道,PHP-FPM 就是一个FastCGI进程管理器,是对于 FastCGI 协议的具体实现,它负责管理一个进程池,来处理来自Web服务器的请求。
PHP-FPM通信方式
在PHP使用FastCGI连接模式的情况下,Web服务器中间件如Nginx和PHP-FPM之间的通信方式又分为两种,TCP模式和套接字(unix socket)模式
1 | 1. TCP模式即是PHP-FPM进程会监听本机上的一个端口(默认为9000),然后Nginx会把客户端请求数据通过FastCGI协议传给9000端口,PHP-FPM拿到数据后会调用CGI进程解析 |
利用SSRF漏洞攻击FastCGI是在TCP模式下进行
2.FastCGI攻击原理
FastCGI协议
1 | HTTP协议是浏览器和服务器中间件进行数据交换的协议 |
Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端(PHP-FPM),语言后端(PHP-FPM)解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件
record的头固定8个字节,body是由头中的contentLength指定,其结构如下:
1 | typedef struct { |
1 | 语言端(PHP-FPM)解析了FastCGI头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体 |
其中,header中的type代表本次record的类型,所有值及具体含义如下
服务器中间件和后端语言(PHP-FPM)通信,第一个数据包就是type为1的record,后续互相交流,发送type为4、5、6、7的record,结束时发送type为2、3的record
举个例子,用户访问http://127.0.0.1/index.php?a=1&b=2,如果web目录是/var/www/html,那么服务器中间件(Nginx)会将这个请求变成如下key-value对:
1 | { |
这个数组其实就是PHP中$_SERVER数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充$_SERVER数组,也是告诉FPM:“我要执行哪个PHP文件”
当后端语言(PHP-FPM)拿到由Nginx发过来的FastCGI数据包后,进行解析,得到上述这些环境变量。然后,执行SCRIPT_FILENAME的值指向的PHP文件,也就是/var/www/html/index.php
漏洞原理
到这里,PHP-FPM FastCGI未授权访问漏洞也就呼之欲出了。PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造FastCGI协议,和FPM进行通信。
此时,我们自行构造SCRIPT_FILENAME的值,就可以控制PHP-FPM执行任意后缀文件,如/etc/passwd
但是,在PHP5.3.9之后,FPM默认配置中增加了security.limit_extensions选项
1 | ; Limits the extensions of the main script FPM will allow to parse. This can |
其限定了只有某些后缀的文件允许被FPM执行,默认是.php。
因此,想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。已存在的PHP文件名获得有两种方法:
1 | 通过系统的信息收集、爆破、报错获得某个PHP文件名及其路径 |
现在,拿到了文件名,我们能控制SCRIPT_FILENAME,却只能执行目标服务器上的文件,并不能执行我们想要执行的任意代码,但我们可以通过构造type值为4的record,也就是设置向PHP-FPM传递的环境变量来达到任意代码执行的目的
PHP.INI中有两个有趣的配置项,auto_prepend_file和auto_append_file
1 | auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件 |
若我们设置auto_prepend_file为php://input(allow_url_include=on),那么就等于在执行任何PHP文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在FastCGI协议 Body中,它们就能被执行了
那么我们如何设置PHP.INI中auto_prepend_file的值呢?
我们可以通过PHP-FPM的两个环境变量,PHP_VALUE PHP_ADMIN_VALUE来设置PHP.INI
最终,我们设置向PHP-FPM传递的环境变量:
1 | { |
最后两行设置auto_prepend_file = php://input且allow_url_include = On,然后将我们需要执行的代码放在Body中,即可执行任意代码(见文章开头漏洞复现)
3.SSRF攻击本地的PHP-FPM
生产环境中,除非测试或者图方便之外,PHP-FPM是极少开放在公网的,绝大部分都是启动在本地即监听127.0.0.1:9000地址,这种情况下,如果服务器端存在SSRF漏洞,那么我们就可以借助SSRF来攻击本地PHP-FPM服务,达到任意代码执行的效果
我们通过CTFHub中的一道SSRF FastCGI协议题目具体进行利用
根据前面几篇SSRF系列的文章,我们对gopher协议已经有所了解
1 | gopher://<host>:<port>/<gopher-path>_后接TCP数据流 |
当后接TCP数据流为我们构造的恶意FastCGI协议报文,即可执行恶意命令
根据前文的FastCGI攻击原理分析,我们需要满足三个条件:
PHP版本要高于5.3.3,才能动态修改PHP.INI配置文件(题目环境已满足)
知道题目环境中的一个PHP文件的绝对路径
PHP-FPM监听在本机9000端口(题目环境已满足)
打开题目链接,我们访问index.php会被重定向,其他任意PHP文件都返回404,说明存在index.php
1 | PHP文件的绝对路径:/var/www/html/index.php |
方法一
所需条件都满足,我们利用题目附件中P牛的EXP:fpm.php
我们在本机监听9000端口,然后运行fpm.py将恶意FastCGI协议报文数据打在本机的9000端口,保存为exp.txt
监听9000端口
1 | nc -lvvp 9000 > exp.txt |
编写exp_urlcode.py将exp.txt进行urlencode编码并输出
1 | from urllib import quote |
然后进行二次编码后将最终的payload内容放到?url=后面发送过去(这里需要进行两次编码,因为这里GET会进行一次解码,curl也会再进行一次解码)
使用中国蚁剑成功连接webshell,在其根目录下即可找到flag
方法二
gopher工具生成payload
与方法一一样,将payload二次编码后发送,然后中国蚁剑成功连接webshell,在其根目录下即可找到flag