冰蝎流程图如下:
冰蝎是先请求服务端,服务端判断请求之后生成一个128位的随机数,并将这个128位的随机数写入到session里面,并将这个128位的随机数返回给客户端,但是客户端并不会使用这个key作为之后的通讯的key,而是会继续重复上面过程,不断获取key,直到满足特定条件之后,才会确定是最终的key。客户端会保存这个key和响应报文里面的set-cookie的值。这个key就是之后客户端和服务端进行通讯的密匙。
冰蝎通信大致分为两个阶段。
Attacker通过GET方法或者POST方法形如下图这样请求服务器密钥。
服务器使用随机数MD5的高16位作为密钥,存储到会话的SESSIONID中,并返回密钥给attacker。密钥可通过wireshark抓包看到,见下图。
在wireshark中即可捕获到冰蝎客户端与服务器通信的数据包。
1.客户端把待执行命令作为输入,利用AES算法或XOR运算进行加密,并发送至服务端;
2.服务端接受密文后进行AES解密或者XOR运算解密,执行解密后的命令
3.执行结果通过AES加密后返回给attacker
1.追踪HTTP流即可看到冰蝎客户端与服务器的完整通信过程。
2、分析冰蝎与服务器的通信过程。
3.当冰蝎第一次访问服务器webshell时,以GET方式提交随机数字,因此服务器将会生成16位的随机字符串,写入session后通过print函数将密钥返回客户端,冰蝎第二次访问服务器时以相同方式更新密钥。
在服务器开启 PHP OpenSSL 扩展脚本的情况下,冰蝎密文采用对称加密算法AES加密,加密后还会进行 base64 编码。在客户端发起密钥协商后,会返回16位明文随机密钥。
AES在线解码 (http://tools.bugscaner.com/cryptoaes/)
BASE64在线解码(https://tool.oschina.net/encrypt?type=3)
经过一次密钥产生与一次密钥更新后,双方开始以对称密钥进行加密通信,首先是冰蝎向服务器发送加密数据,而解密函数在webshell中:
我们通过解密方式解密请求包数据:
1. 获取密钥
从请求密钥的数据包中获取密钥:29ab481053a0ebeb
2. 获取请求密文、返回密文
3. 借助在线解密解码平台
(1)输入密钥和请求密文,解密后为 base64 编码;base64解码
(2)接着进行解密喝解码
(3)输入密钥和返回密文,解密后为base64编码;base64解码
梳理一下流程:
【1】冰蝎将AES加密数据发送至服务器webshell;
【2】webshell检测到没有以GET方式提交的pass参数的值,因此调用openssl扩展,结合冰蝎发送的数据和session中的密钥进行AES解密,得到解密后的载荷assert|eval(base64_decode('QGVy......;
【3】以|为分隔符,将解密后的载荷分隔为两部分,将执行载荷的过程放入自定义类C的魔术函数__construct()中;
【4】实例化自定义类,触发__construct()函数,执行eval(base64_decode('QGVy......;
【5】执行时,首先进行base64解码,然后执行解码后的代码,代码中定义了result数组,result数组有两个键值对:
'status':base64_encode("success")
'msg':base64_encode("57178bd5-7c2a-4c58-a6e8-56e2a2cf6223")
【6】然后从session中取得密钥,对result数组加密,将加密结果输出到客户端;
(4)冰蝎收到加密数据后,在内部进行解密(解密代码与解密的过程在冰蝎内部完成,我们看不到)
但是我们可以自行解密,查看服务器发送至冰蝎的真实数据:
总结冰蝎在流量交互中的特征,这些特征可分为两类。一类是可绕过特征,这类特征攻击者可通过构造报文进行绕过,致使设备检测不到冰蝎 webshell 特征。另一类是非可绕过特征,攻击者在某些情景无法更改 HTTP 某些字段,致使有固定报文字段可供设备检测。使用单个特征误报较高,但多个特征配合使用可降低误报,推荐多个特征搭配使用,进一步提升特征检测的准确性。
Accept是HTTP协议常用的字段,但冰蝎默认 Accept 字段的值却很特殊,这个特征存在于冰蝎的任何一个通讯阶段。如下:
Accept: text/html,image/gif, image/jpeg, *; q=.2, */*; q=.2
请求体Accept字段
冰蝎内置了十余种 UserAgent ,每次连接 shell 会随机选择一个进行使用。如果发现历史流量中同一个源IP访问某个URL时,命中了以下列表中多个 UserAgent ,可基本确认为冰蝎特征。
冰蝎通讯默认使用长连接,避免了频繁的握手造成的资源开销。默认情况下,请求头和响应头里会带有 Connection。
Connection: Keep-Alive
密钥传递时,URI只有一个参数,key-value型参数,只有一个参数。Key是黑客给shell设置的密码,一般为10位以下字母和数字,很少有人设置特殊字符做一句话密码的(少数情况我们不考虑)。而Value一般是2至3位随机纯数字。另外webshell的扩展名一般为可执行脚本,
如下:
\.(php|jsp|asp|aspx)\?(\w){1,10}=\d{2,3} HTTP/1.1
在加密通讯过程中,无URL参数。如下:
\.(php|jsp|asp|jspx|asa) HTTP/1.1
加密所用密钥是长度为16的随机字符串,小写字母+数字组成。密钥传递阶段,密钥存在于Response Body中。正则如下:
^[a-fA-F0-9]{16}$
在加密通讯时,php/jsp shell 会提交base64编码后的请求数据。用如下正则便可以很好的匹配。数字20是指定的字符出现至少20个才会匹配。正则如下:
\r\n\r\n[a-zA-Z\d\+\/]{20,}
该特征同样存在于加密通讯时,在返回包中的数据是加密后的二进制数据。这里使用正则的“非”匹配二进制非常见字符。正则如下:
” [^\w\s><=\-‘”\:\;\,\!\(\)\{\}][\w]{2}[^\w\s><=\-‘”\.\:\;\,\!\(\)\{\}][a-zA-Z\d]{2}”
主要过程就是获取key和保存cookie之后,获取服务端信息,执行命令,文件操作,数据库操作等都是使用这个key和cookie进行操作,对执行的代码动态生成class字节数组,然后使用key进行aes加密,再进行base64编码,并用post方式发送数据。接收服务端返回的数据时,先使用key进行解密,解密之后的数据一般是使用了base64编码,解码之后就可以获取服务端返回的明文数据。
反编译Behinder.jar包,其核心代码在net.rebeyond.behinder包内
主要源代码:
冰蝎的原理为上传一个class字节码,通过调用classloader的defineClass方法,将class字节码,转换为Class。实例化上传的这个类,通过equal方法传递PageContext。获取到PageContext,间接获取到Request、Response、Seesion等对象。如HttpServletRequest request=(HttpServletRequest) pageContext.getRequest()
其核心部分是协商密钥getKeyAndCookie
客户端打开和服务端的连接之后,会先调用BasicInfoUtils类,在BasicInfoUtils类的getBasicInfo方法里,会调用ShellService的构造方法新建ShellService类。而在ShellService类里面的构造方法,会调用Utils的getKeyAndCookie方法。
Utils.getKeyAndCookie方法:
判断得到的密匙rawKey_1之后,进入循环调用getRawKey方法,并获取rawKey_2,并且将rawKey_1和rawKey_2进行异或操作(
如果rawKey_1和rawKey_2两个值不相同,则异或结果为1。如果rawKey_1和rawKey_2两个值相同,异或结果为0。
)。获取rawKey_2的方法和获取rawKey_1基本是一样的。
获取服务器基本信息getBasicInfo
在获取了cookie和key之后,BasicInfoUtil的getBasicInfo就会调用ShellService的getBasicInfo方法来获取放了木马的服务器的基本信息
生成paylaod
由于是jsp,获取对应的字节内容先AES加密,再base64编码返回下
就是直接说的利用POST发送payload了然后返回执行结果,分析完毕
core包
冰蝎的主要逻辑代码。
Crypt.java和Decrypt.java 涉及到server端语言的加密解密
Params.java 调用不同语言的payload,返回其字节序列
注意:
如果攻击者发送的请求不是文本格式的源代码,而是编译之后的字节码(比如java环境下直接向服务器端发送class二进制文件),字节码是一堆二进制数据流,不存在参数;攻击者把二进制字节码进行加密,防御者看到的就是一堆加了密的二进制数据流;攻击者多次执行同样的操作,采用不同的密钥加密,即使是同样的payload,防御者看到的请求数据也不一样,这样防御者便无法通过流量分析来提取规则。
Params类
要让服务端有动态地将字节流解析成Class,但Java并没有提供直接解析class字节数组的接口。不过classloader内部实现了一个protected的defineClass方法,可以将byte[]直接转换为Class。也定义了一个类t继承classloader,调用时返回父类的defineClass方法,就解决了protected的限制,返回解析后的Class。当然这里用反射机制来实现也是可以的。这样就可以直接动态解析并执行编译好的class字节流了,但这里就必须提到java的asm框架。
ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
需要执行的命令是编码在class文件中的,因为class是已编译好的文件。但不能每次执行命令时都编译payload,那么就需要payload可以接收参数。为了参数被waf拦截,自然也是二进制流,所以使用ASM框架来动态修改class文件中的属性值。从上面的代码就可以看出,只需要向getParamedClass方法传递payload类名、参数列表即可获得经过参数化的payload class。
而且.net,asp的实现都是类似的,只是细节上有所不同,而.net的实现是使用编译好的dll文件
在payload中的代码就无法查看
PluginUtils类
ShellEntity类
Crypt(加密类)
在Crypt类中分别有对应的不同server端语言的加解密方法。
继续看各自的加解密方法,这里以C#的为例。
接着看php和asp的加密方法,根据类型不同加密,进行不同的加密。
在客户端集成了不同语言shell的管理,所以就复用了部分代码,下面php执行异或操作的操作就是直接调用asp的代码实现的。
冰蝎在通信过程中使用AES加密,Java和.Net默认支持AES加密,php环境中需要开启openssl扩展,v2.0更新以后,PHP环境加密方式根据服务器端支持情况动态选择,不再依赖openssl,所以在上面代码中不支持AES的情况就直接调用asp的代码。
检测请求方式:Request.method= GET
检测请求资源:Request.url= /[\w.]*.[a-zA-Z]{3,4}\?\w{0,20}=\d{0,10}
检测服务端响应(仅16位密钥):Response.body.startwith =\w{16}
在实际检测中,需要注意如:shell的名字、后缀(可能利用解析漏洞等)、密码的长度等等,实际在目前的版本中都存在获取密钥这个请求。
V1.0请求包详情如下:
V2.0.1请求包详情如下:
冰蝎检测的难点在于在通信过程中数据通过加密传输,难以提取特征,下面我们比较一下两个版本之间在连接过程中发起的GET请求。
冰蝎2默认Accept字段的值很特殊,而且每个阶段都一样。
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Agent字段
冰蝎内置了十余种 UserAgent ,每次连接 shell 会随机选择一个进行使用。但都是比较老的,容易被检测到,但是可以在burp中修改ua头。
c.Content-Length
Content-Length: 16, 16就是冰蝎2连接的特征。
REF:
https://zgao.top/
https://xz.aliyun.com/t/7300
https://zhuanlan.zhihu.com/p/571463343