前言
前段时间不是出题就是给队内wiki整理资料,自己学习倒是懒了下来,摸了摸了,回头看来一下之前在比赛碰到几次,但是还没深入研究的,总结一下做个笔记
概念
官方对它的定义是FastCGI 进程管理器(FPM)
,用来替换PHP FastCGI
的大部分附加功能,缓解高负荷网站的压力,具体解释可见官网https://www.php.net/manual/zh/install.fpm.php
那么问题来了,fastcgi
又是什么呢?其实phper肯定都用过它,只不过可能没有深入去了解而已
Fastcgi
它其实就是一个通信协议,跟http
那些一样的,都是进行数据交换的一种通道
我们都知道http
协议是浏览器和服务器中间件进行数据交换用的协议,类似的,fastcgi
就是服务器中间件和某个语言的后端进行数据交换所使用的协议了,和http
协议一样,fastcgi
协议也是有消息头和消息体的
为什么会有这个协议呢?一开始服务器只需要处理html那些静态的页面,但是随着时间的发展,出现了php那些动态的语言,所以就需要解释器了,可是解释器怎么跟服务器进行通信呢,于是就出现了cgi
协议,可是后来的网站越做越大,服务器每次收到一个请求都需要去fork一个cgi
进程,占用资源太大,于是就出现了改良版本,就是fastcgi
,每次处理完请求后,不会kill掉这一个进程,继续保留这个进程,让这个进程一次处理多个请求
但是需要注意一点,Apache把PHP
作为一个模块集成到Apache的进程(httpd
)中运行,这种mod_php
的运行模式运用的不是php-cgi
,而Nginx服务器则是通过fastcgi
进行php通信的
消息头
主要消息头有
1 |
Version: 用于表示 FastCGI 协议版本号。 |
消息类型
1 |
BEGIN_REQUEST: 从 Web 服务器发送到 Web 应用,表示开始处理新的请求。 |
交互过程
既然上面都讲了那就顺便把交互过程也讲了(菜鸡好啰嗦啊……..
1 |
首先,虽然服务器收到用户的请求,但是请求的处理都是交给web应用完成的。服务器会尝试通过套接字(unix或tcp)连接到fastcgi进程。 |
php-fpm
介绍了fastcgi
,那么php-fpm
又是什么呢?其实就是一个fastcgi
协议的解析器,Nginx等服务器的中间件将用户请求按照fastcgi
规则打包好后通过tcp传给的就是php-pfm
过程如下:fpm
进程包含master
进程和worker
进程,master
进程负责监听,而worker
进程嵌入了php解释器,负责处理php代码
tcp模式下的php-fpm未授权访问攻击
因为fpm
对两个进程通信没有安全性验证,所以如果我们伪造一个fastcgi
规则封装的数据给fpm
解析的话,自然他也是会进行解析的,所以这个时候就可以造成任意代码执行漏洞了
我们先看fastcgi
协议封装的数据格式
1 |
typedef struct { |
php解释器解析了fastcgi
头以后,拿到了contentLenght
,然后在tcp流里面读大小等于contentLength
的数据,就拿到body
的内容
至于padding
,则是由paddingLenght
去指定长度的,若不需要padding
,则将paddingLength
置为0
而type
则要看下图
当type
设置为4的时候,设置环境变量的请求中就会有如下值:
1 |
{ |
php-fpm
会执行SCRIPT_FILENAME
所指的文件,但是php5以后fpm增加了security.limit_extensions
的选项,导致我们只能控制php-fpm
执行php文件
1 |
; Limits the extensions of the main script FPM will allow to parse. This can |
现在我们可以控制任意一个php文件,但是内容还不是我们能可控制的额,这就要用到另一个参数了,fastcgi
会将SCRIPT_FILENAME
的文件交给worker进程解析,这个过程是没有办法控制变量的,但是php-fpm
可以设置环境变量
1 |
'PHP_VALUE': 'auto_prepend_file = php://input', |
先去看一眼php配置手册
所以我们按照如上方法设置环境变量的话,就能让php脚步执行php://input
的值,因此我们就能就行任意代码执行了,那么恶意的数据放在哪呢,当然就是在fastcgi
协议的body
里面了
讲到这里,接下来我们就是要去写脚本了
原理其实就是写一个fastcgi
的客户端,然后修改发送数据成恶意代码就行
菜鸡搭了半天docker搭不起来,最后只能用官方的php-fpm
镜像去搭建了
接下来就用ph牛的脚本打过去
unix套接字模式下的php-fpm攻击
既然tcp模式下可以进行php-fpm
攻击,那么我们尝试一下unix套接字模式下是否可以进行攻击呢
这个知识点直接用一个例题进行讲解,就是*ctf
的echohub
试一下url/?source=1
可以拿到源码
但是解码出来是乱码,其实就是一个混淆脚本
大佬们都是ida直接动态调试出来,菜鸡太菜了,只能找个解密网站将它解密,出来源码是这样的
1 |
|
接下来就是源码审计了
出题的师傅tql,这是一个用php写的栈,orz
首先看到一个沙箱
1 |
foreach ($func as $f){ |
先看sandbox.php
可以看到,只要函数名包含了file
、open
、read
、write
等字符就会被禁用
同时也将那些内置的php函数名称存储在$func
变量中
1 |
$func = get_defined_functions()["internal"]; |
接着审计index.php
,文件开头是以当前时间作为种子进行随机数播种
1 |
$seed = time(); |
然后将沙箱的那个$func
转化为地址=>函数名
的$plt数组,而且开启alsr
保护,实现键值随机
1 |
function aslr(&$value,$key) |
栈内初始化的时候会有canary
机制,在栈内随机初始一个$canary
,用来检测栈是否缓冲区溢出
栈内压入局部变量的时候会校验当前栈内的$canary
和$canarycheck
是否一致,$canary
被覆盖了就会报错,然后程序就会退出,因此我们覆盖的时候要保证$canary
不被覆盖
1 |
function gen_canary(){ |
phpinfo函数的地址被默认放在ebp+0x4
,也即是函数结束后eip
的下一跳的地址,然后去$plt
映射表找到函数名,传给call_user_func
执行,所以正常的交互都是返回phpinfo
的信息
1 |
public function call() |
web蒟蒻一脸懵逼,大概理解过来就是写栈,但是注意canary不要也覆盖过去,然后绕过aslr获取利用恶意函数的地址,控制返回地址,调用恶意函数进行命令执行
接下来就是解题了,首先我们先注意一点,aslr和canary的初始化都是根据随机数进行的,而随机数的种子则是请求的时间time,所以这只是一个伪随机数,我们可以利用它去预测函数地址
curl一下过去发现时间是跟本地时间一样的,因此我们可以对rand函数产生的随机数进行预测,使alsr和canary都失效
菜鸡还是太菜了,脚本写了执行不成功,就不贴了
突然发现前面说了一大堆都是栈溢出漏洞(捂脸,那么接下来就是php-fpm
的unix
套接字攻击漏洞
php文件如下
1 |
|
可以看到设置了unix
模式,同样的,配置文件也设置套接字模式,这个时候启动php-fpm
就会发现监听服务已经换成socket
监听了
exp如下
1 |
#!/usr/bin/python |
这里有个bug,菜鸡也不太会改,等日后水平上去了可能才能改得动(捂脸
tcp模式下ssrf攻击本地的php-fpm
我们记得ssrf攻击有个是利用gopher://
进行的攻击的方法URL: gopher://<host>:<port>/<gophar_path>_后接TCP数据流
因为gopher
协议可以直接发送tcp协议流,所以我们可以直接将数据流进行urlencode编码然后构造代码
利用脚本如下
1 |
#!/usr/bin/python |
服务器测试代码如下
1 |
|
执行python fpm_ssrf.py -c '<?php echo
id; ?>' -p 9000 127.0.0.1 /var/www/html/index.php
生成payload打过去就行
但是要注意一点,payload需要urlencode两次,因为nginx会解码一次,然后php-fpm解码一次
或者用GitHub上的脚本也行
FROM:Xi4or0uji
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论