查看原文
其他

九维团队-绿队(改进)| shiro反序列化漏洞源码分析

symn.su 安恒信息安全服务 2022-11-05


shiro介绍


Apache Shiro是企业常见的JAVA安全框架,执行身份验证、授权、密码和会话管理。只要rememberMe的AES加密密钥泄露,无论shiro是什么版本都会导致反序列化漏洞。


shiro漏洞原理


Apache Shiro框架提供了记住我的功能(RemeberMe),用户登录成功后会生成经过加密并编码的cookie。cookie的key为RemeberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。


在服务端接收cookie值时,按以下步骤解析:

1.检索RemeberMe cookie的值

2.Base 64解码

3.使用ACE解密(加密密钥硬编码)

4.进行反序列化操作(未作过滤处理


在调用反序列化的时候未进行任何过滤,导致可以触发远程代码执行漏洞。


处理流程如下:


简单的环境搭建

shiro1.2.4的war包链接:https://pan.baidu.com/s/1i-XEEZsoBgzMzSwclAh2gA 提取码:6mzq

*左右滑动查看更多


将war包放到tomcat的webapps下,双击bin目录下的startup.bat,然后访问http://localhost:端口/shiro/login.jsp即可。


使用root/secret登录,抓包,服务端会返回一串加密的字符。


源码分析

首先关注CookieRememberMeManager,是一个用于处理cookie的manager:

org\apache\shiro\web\mgt\CookieRememberMeManager.java

*左右滑动查看更多



这个manager管理器会在默认的管理器DefaultWebSecurityManager里面对其进行实例化。

在CookieRememberMeManager里面,有关于rememberMe的序列化和反序列的函数:


对反序列化的过程进行分析,进入getRememberedSerializedIdentity。


服务器获取request和response里的cookie,先进行base64解码,但看文章一开始的流程图,是还有一个aes解密的,但到这里,已经返回null了,说明aes解密在另外一个函数里解决了,另外一个函数里肯定包含

getRememberedSerializedIdentity。


查找一下哪里调用了

getRememberedSerializedIdentity:


跳转到源码,发现下面是调用了

convertBytesToPrincipals:


这里插播一下,因为对SubjectContext对象的内容是什么比较感兴趣,于是去找调用,里面存放的是token和info信息:


继续找createSubject的调用,是在login方法里面:


继续找login的调用:

会发现其实到最后传入的参数就是request和response了,即客户端发起的请求,而AuthenticationToken其实是

UsernamePasswordToken。


插播结束,继续之前的内容。跟进去,可以看到这个函数做了两步操作,一个解密一个反序列化,解密应该就是aes解密,就不看了。


看反序列化函数。


接着跟进,看到是一个实现泛型的序列化接口:

点击绿色的标签:


看到deserialize的两个实现:

点击默认的序列化类,调用的是原生的反序列化接口,可以看到里面有readObject方法。


接下来只需要找到aes密钥那我们就可以自己构造参数了,回到刚刚的convertBytesToPrincipals函数跟进到decrypt。

可以看到先将getCipherService实例化为cipherService再去调decrypt进行解密。

跟进getDecryptionCipherKey方法,直接返回一个常量。

找到写它的地方:

跟进去可以看到是用来设置密钥的函数:

接着找哪里调用了它:

可以看到是用来设置密钥的函数。

查看哪里调用,可以看到

AbstractRememberMeManager函数里的setCipherKey是一个常量,跟进去:

然后看到密钥是一段固定值:

那么现在有了密钥我们就可以自己构造数据包,就是序列化aes加密base64加密,然后把它放到正常的执行流程里就可以利用了。


再看一下序列化的过程,就是上面的逆过程。


我们登录成功的时候,服务器生成cookie然后返回给客户端,这个就是序列化的过程:

rememberSerializedIdentity是将已经加密的aes数据进行base64编码,然后返回到前端,找一下调用的地方:

convertPrincipalsToBytes应该就是序列化和aes加密的函数,跟进

convertPrincipalsToBytes:

可以看到是先将认证信息进行序列化,然后aes加密:

serialize函数一直跟下去,就是原生的序列化方法:

返回convertPrincipalsToBytes,进入encrypt方法:

同样也是先实例化CipherService对象,然后调用encrypt方法进行aes加密:

getEncryptionCipherKey是获取key,后面的就和上面一样了,不做重复。

我们返回rememberSerializedIdentity方法,去查看它的调用:

发现最后是到DefaultSecurityManager的login方法,简单看一下,显示进行认证,如果最后成功登录,则跳到rememberMeSuccessfulLogin:


然后继续跟进,看到的是会先进行一个清楚的操作,然后生成一个新的认证。

至于具体的内容,后面就不深入,因为最后就会到rememberSerializedIdentity方法,有兴趣可以自己深入一下。


经过上面的调试,可以看出反序列化的过程中,先对SubjectContext的内容进行base64解密操作,然后先是aes解密,解密根据一个key进行解密,再调用原生的反序列化函数进行反序列化,这个过程没有进行过滤,且key不是随机的而且是我们能够得到的,那就意味着在已知key的情况下就可以复原这个过程。


思路:

我们构造恶意的poc,对其进行序列化然后aes加密(key已知)再base64加密,然后放入cookie中向服务器发起请求,这时服务器就会进行反序列化的操作,先base64解密、aes解密,最后就是反序列化操作,反序列化的过程并没有过滤,那么就能够进行一些恶意的操作。


修复方法


去掉或者替换默认的秘钥。


1、去掉默认秘钥


2、替换默认秘钥

这里可以自定义一个,或者使用下边自动生成的方式。


新建类GenerateCipherKey,添加静态方法getCipherKey()。


在初始化AbstractRememberMeManager的时候调用。




—  往期回顾  —



关于安恒信息安全服务团队安恒信息安全服务团队由九维安全能力专家构成,其职责分别为:红队持续突破、橙队擅于赋能、黄队致力建设、绿队跟踪改进、青队快速处置、蓝队实时防御,紫队不断优化、暗队专注情报和研究、白队运营管理,以体系化的安全人才及技术为客户赋能。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存