漏洞分析
将断点下在DefaultSecurityManager类的resolvePrincipals方法中
可控的cookie

第一步检查了是否principals为空
什么是principals:可以将shiro中的principals理解为已经成功登陆的用户的一个上下文,保存了认证用户的一些环境
- 如果principals为空的话,也就是当前用户没有已经认证的上下文,然后检查是否设置了rememberMe,也就是getRememberedIdentity方法
- image.png
在这里,获取rememberMeManager,并且从rememberMeManager中获取认证用户的信息
-
image.png
可以看到通过getRememberedSerializedIdentity取得了bytes变量的值,进去看看getRememberedSerializedIdentity是怎么取值的

这个地方是重点:
可以看到,通过WebUtils这个工具类获取了原生的res和req,然后从其中读取了cookie的值

在readValue中,获取了name为rememberMe的cookie的值,也就是我们在登陆的时候,如果选择rememberMe在cookie中生成的值,最后将cookie的值返回

硬编码的AES的key
现在我们返回了一个经过base64编码后的加密字符串,接下来对获取到的cookie值进行aes解密
-
先进行base64解码
image.png
第一步先进行padding确保正确的base64解码结果,之后调用Base64.decode进行base64解码
-
进入Base64.decode函数
image.png
对获取到的字符串将其转换为byte数组
之后调用参数为字节数组的decode方法

在这个方法中,将这个字节数组进行base64解码,并且返回对应解码后结果的字节数组
-
再回到getRememberedPrincipals方法中,这一步我们已经获取到了base64解码以后的字节数组
image.png
之后需要将这个字节数组变为principals对象
-
进入convertBytesToPrincipals
image.png
image.png
可以看到getCipherService的返回值为aes加密服务
继续跟入decrypt函数

如果cipherService不为空的时候,会调用对应cipherService的decrypt函数
在这里我们需要传入一个加密的字节数组,一个解密的key
-
进入getDecryptionCipherKey函数,看一下怎么获取到解密的key
image.png
这里直接返回了对应的decryptionCipherKey属性,看下这个属性是怎么被赋值的

通过调用setDecryptionCipherKey来赋值,再搜索setDecryptionCipherKey的调用

通过setCipherKey来赋值,继续寻找setCipherKey的调用

可以发现,在类的构造方法中,对Key进行了赋值,而且这个key是一个常量,跟入看一下这个常量的值

可以发现,这个key的值是硬编码的一段字符串,而aes是对称加密算法,我们可以自己去伪造rememberMe的值
-
进入对应的decrypt方法
image.png
这里进行了iv的初始化,可以看到iv的值,之后调用了decrypt方法
- image.png
在这里进行了cookie的解密,可以通过动态调试获取到所有解密所需的key,iv,加密模式,最后获取到解密以后的数据
反序列化

可以看到获取到的byteSource中有一段很熟悉的特征:rO0AB...,这是java序列化后的数据,这代表了,解密以后的数据其实是一个java对象序列化之后的结果,那么在之后必定会进行java序列化对象的反序列化操作,继续向下跟

在这个地方调用了deserialize方法对获取到的数据进行反序列化操作

看一下getSerializer()的返回值

所以进入DefaultSerializerdeserilize方法:



将解密的数据用ObjectInputStream包装,并且调用了readObject方法,也就是反序列化的入口点
为什么可以利用
可以看到,整个漏洞是由几个漏洞串联起来的:
- Cookie的内容是用户可控的
- 最重要的一个地方:Cookie的加密字符串是硬编码的,所以导致我们可以任意伪造RememberMe Cookie的值
- 因为这个Cookie加密的其实是一个序列化后的对象,所以在后面进行反序列化的时候,因为我们可以伪造cookie,那么我们也就可以控制反序列化时候的对象,从而导致反序列化漏洞









