大数跨境

流量加密也不怕!多种姿势检测冰蝎

流量加密也不怕!多种姿势检测冰蝎 星维九州
2019-10-16
1
导读:冰蝎是当下最流行的WebShell客户端,它可以在HTTP明文协议中建立加密隧道,以躲避安全设备的检测。本文将总结冰蝎(v2.0.1)的通讯特征,并将特征落地为正则表达式,不管是传统的还是新型的IDS


冰蝎是当下最流行的WebShell客户端,它可以在HTTP明文协议中建立加密隧道,以躲避安全设备的检测。对于工作于七层的IDS,可以检测HTTP完整的双向内容,检测冰蝎并非难事。而对于传统的工作于四层的IDS,只能检测TCP层的单个“帧”,要想检测冰蝎,似乎难于登天。本文将总结冰蝎(v2.0.1)的通讯特征,并将特征落地为正则表达式,不管是传统的还是新型的IDS/WAF,都可用它检测冰蝎。(注:本文讨论的是webshell上传之后与冰蝎的通讯特征,属于事后检测。而不是正在上传webshell时的流量特征)


  冰蝎通讯原理

冰蝎使用对称加密算法。加密过程总共分三步:

  A  第一步,密钥传递阶段客户端向服务器请求密钥,密钥传递是完全明文的。

图1 密钥传递阶段

  B  第二步,算法协商阶段。SSL握手时会把自己支持的加密算法列表明文发给对方,然而这样会暴漏更多的特征(JA3指纹),冰蝎很显然是考虑到了这一点,它很聪明的将一串payload用不同的算法加密发送给服务器,如果服务器成功解密,那么接下来的通讯便用这种算法。如果算法不对解密失败,那么响应为空,冰蝎继续更换另一种加密算法进行尝试,直到成功为止。加密算法一般为AES128和抑或。

图2 密钥协商阶段

  C  第三步,正式通讯阶段。客户端使用上述密钥加密payload,POST给服务端,服务端解密后执行将结果又以加密方式作为Response Body返回给客户端。

图3 正式通讯阶段

第二步与第三步其实用的是同一个通讯接口,因此特征类似。

 静态特征 

我们总结了8个冰蝎的静态特征,这些特征可分为两类,一类是强特征,这类特征误报较低,可单独使用。另一类是弱特征,单独使用误报较高,但多个弱特征配合使用可降低误报,达到和强特征一样的效果。推荐与强特征搭配使用,进一步提升强特征的准确性

  A  弱特征1:密钥传递时URL参数

密钥传递时,URI只有一个参数,key-value型参数,只有一个参数。Key是黑客给shell设置的密码,一般为10位以下字母和数字,很少有人设置特殊字符做一句话密码的(少数情况我们不考虑)。而Value一般是2至3位随机纯数字。
另外webshell的扩展名一般为可执行脚本,因此正则为

\.(php|jsp|asp|jspx|asa)\?(\w){1,10}=\d{2,3} HTTP/1.1

 
图4 密钥传递阶段的请求

  B  弱特征2:加密时的URL参数

在加密通讯过程中,没有URL参数。是的,没有参数本身也是一种特征。

\.(php|jsp|asp|jspx|asa) HTTP/1.1

 

  C  强特征3:Accept字段(可绕过)

Accept是HTTP协议常用的字段,但冰蝎默认Accept字段的值却很特殊,我们也没有想明白为什么要设置这么一个奇怪的值。这个特征存在于冰蝎的任何一个通讯阶段。
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
冰蝎支持自定义HTTP Header,因此该特征可以被绕过。

  D  强特征4:UserAgent字段(可绕过)

冰蝎内置了十余种UserAgent,每次连接shell会随机选择一个进行使用。
以下UserAgent列表是从冰蝎的jar包中提取的,可见大多是比较早的浏览器,现在很少有人使用。而且有些国产浏览器甚至精确到了小版本,众所周知,很多国产浏览器是默认自动更新,正常用户很少用过早的版本,因此可以作为强特征使用。
列表中有少量标红的UserAgent,目前用户量较大,不可作为强特征。
如果发现历史流量中同一个源IP访问某个URL时,命中了以下列表中多个UserAgent,那基本确认就是冰蝎了。
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1 
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0 
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50 " BOpera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.5
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)
Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1) , 7
Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)
Mozilla/5.0 (Windows; U; Windows NT 6.1; ) AppleWebKit/534.12 (KHTML, like Gecko) Maxthon/3.0 Safari/534.12 
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)
Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.33 Safari/534.3 SE 2.X MetaSr
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)
Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1 QQBrowser/6.9.11079.20
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E) QQBrowser/6.9.11079
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
 
同样,UserAgent可由黑客自定义,因此该特征可能会被绕过。

  E  强特征5:传递的密钥

加密所用密钥是长度为16的随机字符串,小写字母+数字组成。密钥传递阶段,密钥存在于Response Body中,如图。
图5 密钥传递阶段的响应
因此密钥特征如下:

\r\n\r\n[a-z0-9]{16}$

 
如果不想使用正则,可使用字符串特征,误报会更多。

Content-Length: 16

 

  F  弱特征6:加密数据上行

在加密通讯时,php/jsp shell会提交base64编码后的数据。用如下正则便可以很好的匹配。

\r\n\r\n[a-zA-Z\d\+\/]{20,}


注意这条正则最后的数字20,意思是指定的字符出现至少20个才会匹配。而要匹配这段数据长度至少有几K,在TCP层,如此长的数据不会一次性发送,而是拆分成若干“帧”,逐个发送。工作于4层的IDS只能检测“帧”,而“帧”的长度是不确定的(帧的长度是根据网络情况动态调整的),因此这里保守地只写了20。如果你的IDS工作于七层,可以拼接HTTP完整请求的话,这个值可适当增加,以减少误报。
 

  G  弱特征7:加密数据下行

该特征同样存在于加密通讯时,在Response Body中的数据是加密后的二进制数据。
图6 正式通讯阶段的响应

如何用正则去匹配二进制数据呢?这里我们使用的方法未必是最好的,仅供大家参考。二进制数据中必然有大量不可见的字符,当然也有不少可见字符,它们出现的位置是随机的。如果第一个位置是可见字符,那么之后的6个字符之内有很大概率出现不可见字符。匹配不可见字符使用零宽负行断言的方式,即不认识的字符即为不可见字符。为了增加准确性,本条策略里扩展了“不可见字符”的定义,这里认为的不可见字符,除了“无法显示的字符”之外,还加入了HTML/JSONP中不常见的字符

\r\n\r\n[\w]{0,6}[^\w\s><=\-'"\/\.:;\,\!\(\)\{\}]+


如果用户加载图片/视频等多媒体二进制文件的话也是会引发误报,因此我们再写一条策略,规定MIME类型为html。

Content-Type: text/html


这两条规则是“且”的关系。意思是如果发现text/html类型的文档是二进制的,那么它就是可疑的。
需要注意的是,并不是所有正则引擎都支持“断言”模型。在使用本策略之前,请确认你使用的正则引擎支持断言,并进行严格测试
 

  H  弱特征8:长连接(可绕过)

冰蝎通讯默认使用长连接,避免了频繁的握手造成的资源开销。因此默认情况下,请求头和响应头里都会带有:

Connection: Keep-Alive


这个特征存在于冰蝎的任何一个通讯阶段.

 动态检测 

在大型互联网企业或者IDC中,出口流量动辄十几个G。即便某条规则的误报率只有百万分之一,由于分母巨大,出现的告警量也是相当可观的。为了进一步提高准确性,在“静态特征”生成的告警之上做“动态行为检测”。这一节不再是正则表达式能做到的,而是要靠开发人员编写脚本来实现逻辑上的检测。
按照上述“静态特征”编写两类规则:1.密钥传递规则。2.加密通讯规则。
如果同一个url或者源IP在数秒内内同时命中了这两种规则,那么可以非常肯定的确认是冰蝎了。注意,此处是同一个URL或者源IP,而不是同一个TCP会话,密钥传递和加密通讯未必是同一个TCP会话(源端口会变化)。
依照上面的方法虽然能够确定是冰蝎,但是不知道黑客在服务器上干了什么。因此要想进一步的调查取证,必须解密冰蝎。首先依据“密钥传递规则”匹配出的告警,提取密钥。再依据“加密通讯规则“提取出的加密后的内容进行解密。但是这里对IDS有一个较高要求,就是要提取全量的HTTP请求和响应,大概几K到几百K,中间不允许有截断,因为加密算法的”雪崩效应“,密文即便缺少一个字节也无法解密。
下面是解密PHP Shell通讯的一个简单Demo。
# -*- coding: utf-8 -*-
import base64
from Crypto.Cipher import AES
 
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
# 密钥
key = '4d21a8526f30c7e2'
# 冰蝎的加密请求
http_request = """POST http://xxx:8080/fff2.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=p88ghqvul97j646tl7dgs9hcb7; path=/
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)
Cache-Control: no-cache
Pragma: no-cache
Host: xxx:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 1112
 
60Yp… ….xYA=="""
 
def add_to_16(value):
    # print(k, type(k))
    while len(value) % 16 != 0:
        value += '\0'
    return value
 
 
def aes_decode(k, data):
    # AES解密
    aes = AES.new(add_to_16(k), AES.MODE_ECB)
    data_encode = data.encode()
    data_64 = base64.b64decode(data_encode)
    data_pass = aes.decrypt(data_64).decode(encoding='UTF-8', errors='ignore')
    return data_pass
 
 
def get_http_info(raw):
    # 从HTTP请求中,提取POST Body
    temp_arr = raw.split("\n")
    length = 0
    for one in temp_arr:
        if "Content-Length: " in one:
            length = int(one[16:])
    body = temp_arr[-1]
    if length == len(body):
        return length, body
    else:
        return -1, body
 
 
body_length, body = get_http_info(http_request)
# 提取POST Body
if body_length > 0:
    text_64 = base64.b64decode(body)
    # 把post body进行base64解密,获取原始的二进制密文。
    text_str = text_64
    text_list = []
    # 先尝试进行抑或解密
    for i in range(len(text_str)):
        text_list.append(chr(ord(text_str[i]) ^ ord(key[i + 1 & 15])))
    clear_text = ''.join(text_list)
    if "assert|" in clear_text:
        # assert是payload中的固定明文关键字,带此关键字说明解密成功
        print(clear_text)
    else:
        # 亦或解密失败,再尝试AES解密
        clear_text = aes_decode(key, body)
        if "assert|" in clear_text:
            print(clear_text)
        else:
            # 两种算法解密失败的话,则最终宣告解密失败
           # 密钥错误 或者密文提取不全,都会导致无法解密
            print("无法解密")
else:
    print("无法提取Post Body")
 
 
以上总结的检测规则和方法,部分已经投入使用,并且表现良好。





【声明】内容源于网络
0
0
星维九州
山东星维九州安全技术有限公司,是一家专业的MSS/MDR服务商,提供7*24小时的安全监测与响应能力。公司致力于山东省内的网络安全运营业务。业务覆盖安全监测、安全预警、多维防御、态势感知、快速响应、安全咨询等多种领域。
内容 14
粉丝 0
星维九州 山东星维九州安全技术有限公司,是一家专业的MSS/MDR服务商,提供7*24小时的安全监测与响应能力。公司致力于山东省内的网络安全运营业务。业务覆盖安全监测、安全预警、多维防御、态势感知、快速响应、安全咨询等多种领域。
总阅读0
粉丝0
内容14