前言
之前写过蓝帽杯的One Pointer那题,那题绕过disabled_functions(下文全称DF)利用php-fpm来打,而对话fpm的方式是利用FTP的passive mode,但是看了这位师傅的wp,发现对话fpm的fsockopen被禁了,但是pfsockopen没有被禁,而且在那位师傅的文章中也提到了是改蚁剑的fsockopen为pfsockopen来实现对话fpm的。因此就萌生了尝试改造蚁剑的想法,文章比较水,就是记录一下改造过程和一些坑的地方。顺便梳理以下蚁剑php-fpm的整个攻击流程是怎样的
对话php-fpm
首先我们知道php-fpm有两种模式,一种是tcp,另一种的unix,这就涉及到一些即用的直接对话函数和一些间接发恶意bin的方式
fsockopen
支持tcp和unix。初始化一个套接字连接到指定主机(hostname
)。返回一个文件句柄,之后可以被其他文件类函数调用,如fgets等
1 | fsockopen( |
常见用法:
1 | fsockopen("127.0.0.1", 9001, $errno, $errstr, 5); //tcp |
pfsockopen
和fsockopen基本一样,只不过该连接建立的是持久连接,即只需初始化一次连接,当在脚本执行完后,连接一直不会关闭。
stream_socket_client
也支持tcp/udp和unix
1 | stream_socket_client( |
$remote_socket
:如果是tcp的方式,则需要传入<tcp/udp>://<host>:<port>
,unix的话就和上面一样
常见用法:
1 | stream_socket_client('tcp://127.0.0.1:9001', $errorno, $errorstr, 5); |
SSRF + Gopher
如果提供了SSRF的接口,那么可以直接利用gopher配合ssrf来发送恶意bin,这时我们要改造一下php-fpm的利用脚本,改为输出恶意bin而不直接进行连接或者Gopherus生成,具体可以参考我的这篇文章
SSRF + FTP Passive Mode
FTP Passive Mode可以让服务器访问我们伪造的ftp客户端,然后伪造的客户端欺骗服务器将数据打在php-fpm的服务上,同样实现了恶意bin的发送。生成方式还是在上面的文章中
浅析蚁剑php-fpm攻击结构 & 流程
目录结构
其实蚁剑的攻击方式很简单,关于绕过DF具体的逻辑在[蚁剑安装目录]/antData/plugins/as_bypass_php_disable_functions-master/
,简单介绍一下目录结构
index.js:初始化入口,定义窗口、插件、语言库,新增其他功能选项(插件)的话可以在这里修改
payload.js:集成了绕过DF的几种方法所需要的payload,包括
FastCgiClient
、ProxyScript
、ProxyScriptFsock
等,在需要的时候取出相应的payload或者上传相应的payload到已连接的shell上ext目录:包含了恶意so、dll,用于LD_PRELOAD、php-fpm等需要利用恶意扩展来RCE的地方。
core目录:核心攻击目录,所有攻击方式的逻辑都在core的子目录中,因此我们要改造php-fpm的话,就关注core/php-fpm这个库
core/base.js:Base类,所有攻击方式类的一个父类(可以理解为一个接口),包含generateExt(生成扩展)、uploadProxyScript(上传代理脚本)、CompVersion(比较版本)这几个方法
其他目录:各个攻击模块
language:语言依赖,和模块UI的定义,比如zh.js,以对象的方式存储各个模块显示在UI上的条目及描述说明,供各个攻击模块类在初始化的时候获取
language/zh.js:
core/php-fpm/index.js:
攻击流程
我们重点关注一下core/php-fpm这个目录,就只有一个index.js文件,包含了所有逻辑,其他依赖的so和payload都从上面提到的文件中获取。
- 开头获取一些依赖
createForm
方法初始化构建UI,因此我们要新增选项的话就在这里改
exploit
方法,完整的攻击逻辑,先从createForm方法中初始化好的Form中获取一些攻击参数的信息,如TCP/Unix地址,PHP可执行文件的位置、对话php-fpm的函数选择(就是本次改造的地方)
整个攻击流程:
- 获取攻击参数信息:TCP/Unix地址,PHP可执行文件的位置、对话php-fpm的函数
- 生成并上传扩展至可写目录(恶意so或dll),往扩展里面注入的命令如下,该命令用于在目标某服务器的随机6000+端口上不使用php.ini(默认配置,无DF和open_basedir)起一个web服务,启动成功后我们访问该web服务器就没有DF限制了
1 | let cmd = `${phpbinary} -n -S 127.0.0.1:${port} -t ${self.top.infodata.phpself}`; |
- 从payload.js获取并生成payload(最常见的那个打fpm的php版本payload),然后执行payload,并验证payload是否执行成功,若执行成功,则继续上传.antproxy.php的代理脚本,若不成功,则回显失败不上传代理脚本。验证方式就是利用连接函数去测试是否成功开启了web服务
- 得到.antproxy.php脚本后,该脚本相当于一个代理,我们可以新增一个条目,选择.antproxy.php作为shell文件,该文件构造访问原先shell.php文件的数据包,然后发送给新起的php服务器,然后接受数据。相当于代理访问了shell.php文件(但此时的服务器是没有DF的服务器),连接密码还是原先的密码。
增加对话php-fpm函数的选项
最终UI效果:
增加UI
根据前面所说的几个点,我们大致知道了UI的位置,剩下就是ctrl + F来试了23333
- language/zh.js:给form增加一个func选项
- core/php-fpm/index.js:给新增的func选项增加值
- payload.js:给侧边的DF(函数支持)检测增加
pfsockopen
、stream_socket_client
两个选项
找到这句代码,加上要检测的值即可。
1 | $func_arr = array("fsockopen", "pfsockopen", "stream_socket_client", "iconv", "dl", "putenv", "error_reporting", "error_log", "file_put_contents", "file_get_contents", "fopen", "fclose", "fwrite", "tempnam", "imap_open", "symlink", "curl_init"); |
对话函数选择
这一部分其实可以自由发挥,毕竟知道了结构之后咋写都可以,但这里也有蛮多坑的,因为攻击流程中有很多地方用到对话连接函数,如果改漏了一个,那整个流程可能就会失败。按逻辑来说,就从php-fpm/index.js的exploit方法部分开始改,下面修改几处用到连接函数的地方:
- 第一处:payload.js的FastCgiClient方法,该方法就是攻击php-fpm的payload,原先的逻辑是根据
$_persistentSocket
来选择是否长连接(pfsockopen),这里就随便改一下
- 第二处:php-fpm/index.php,由于fosockopen和stream_socket_client的函数参数不一样,所以还要判断一下
- 第三处:payload.js的ProxyScriptFsock方法,这个方法的内容就是.antproxy.php文件的内容,同样修改一下连接函数
测试 & 出错可能原因
就拿蓝帽杯那题来测一下,在连上蚁剑之后,选择pfsockopen可以一键打通。但是会发现,随着测试次数的增多,大部分情况下却打不通了,之前一直以为是脚本修改哪里出了问题,但经过多次测试,几乎第一次都是能打通的,后面几次就随机通,猜测可能是端口被占用无法开启php服务或者Nginx连接数限制的原因。这里测完第一个后,拿着.antproxy.php重开一个shell,这个shell就没有任何限制了,这里就顺手拿fsockopen、stream_socket_client测试一下。都可以成功(还是概率问题)
总结
蚁剑的整体结构大致了解了一些,改起来还是比较简单的,如果打不通,可以考虑以下几项
- 修改的地方是否出错,可以打印相关参数来看和对比一下
- 修改连接函数的地方是否出错,这是大概率payload打不通的原因
- 要打的端口被占了,概率问题,多试几次。