Shiro反序列化漏洞详细分析

 

0x00 前言

上次讲了最近的shiro权限绕过的漏洞,这次来补充分析一下以前的shiro-550反序列化漏洞。另外学习了shiro-550也可以更好地理解shiro-721漏洞,也就是Shiro Padding Oracle Attack。所以这次就详细地看下shiro反序列化漏洞。

 

0x01 shiro-550介绍

shiro-550(shiro小于1.2.5)主要是由shiro的rememberMe内容反序列化导致的命令执行漏洞,造成的原因是AES密钥被硬编码在shiro源码中,这就导致了可以通过在cookie的rememberMe字段插入payload实现任意代码执行。在这以后shiro使用随机密钥,而不再硬编码,这又造成padding oracle attack导致的shiro-721。

 

0x02 动态详细分析

一、环境搭建

1.首先下载shiro-root-1.2.4,部署到IDEA

2.然后修改一下依赖文件

<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <!--  这里需要将jstl设置为1.2 -->
        <version>1.2</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
<dependencies>

3.用IDEA内置tomcat部署即可

1

二、加密分析

我们进入登陆页面,输入后准备登录

2

在登陆前我们先在AbstractShiroFilter.class#doFilterInternal下断点,登陆时会断到这里。在第158行this.createSubject(request, response)这里,会创建subject,并使用cookie等信息,在解密时会进行分析

3

我们步入executeChain(request, response, chain)后,再单步步入doFilter、InternaldoFilter等等后,最后可以进入到AuthenticatingFilter#executeLogin,用来处理登录

4

再单步步入33行login函数,经过相关判断后,登陆成功,我们来到this.onSuccessfulLogin(token, info, loggedIn)

5

单步步入onSuccessfulLogin函数,继续步入rememberMeSuccessfulLogin函数

6

在获取到RememberMeManager不为空后,步入AbstractRememberMeManager#onSuccessfulLogin

7

我们跟进forgetIdentity函数,发现它处理了request和response请求

8

继续步入this.forgetIdentity(request, response)后单步步入this.getCookie().removeFrom(request, response),获取了配置信息包括rememberMe字段和deleteMe字段,并用addCookieHeader()添加到cookie中

9

之后返回到AbstractRememberMeManager#onSuccessfulLogin。this.isRememberMe(token)检查是否选中登陆时Remember Me选项,之后我们一直单步步入rememberIdentity

10

最后到达这里

11

第一个函数就是转为bytes,我们跟进convertPrincipalsToBytes函数后发现首先将进行序列化,之后由this.getCipherService()获取到加密服务方式不为空后,进入this.encrypt(bytes)阶段

12

我们单步步入encrypt函数。在其中首先获取密码服务为AES/CBC/PKCS5Padding,之后进入this.getEncryptionCipherKey()

13

14

单步步入后,最后可以发现CipherKey就是AbstractRememberMeManager.class开头的DEFAULT_CIPHER_KEY_BYTES=”kPH+bIxk5D2deZiIxcaaaA==”

15

获取CipherKey返回后进入cipherService.encrypt函数中,生成初始化向量ivBytes后,进入具体的加密函数,最后return

16

一步步return bytes后,回到rememberIdentity函数,下面的rememberSerializedIdentity实现了记住序列化身份的功能,跟进

17

在rememberSerializedIdentity里面,进行base64后,将信息加入到cookie中

18

之后就层层返回,直到AuthenticatingFilter#executeLogin处理登录,返回成功登陆

19

三、解密分析

与加密类似,直接在AbstractShiroFilter.class#doFilterInternal下断点,然后单步到DefaultSecurityManager#createSubject

20

单步到resolvePrincipals函数后,继续单步到getRememberedIdentity函数,在其中获取RememberMeManager后进入rmm.getRememberedPrincipals(subjectContext)

21

我们分别步入其中两个函数看看

22

在第一个函数getRememberedSerializedIdentity中,先获取cookie中的值,然后base64解密,生成二进制数后返回

23

第二个函数convertBytesToPrincipals中,先获取解密服务不为空后,将二进制数据传入decrypt函数进行解密,之后return this.deserialize(bytes)

24

在deserialize(bytes)中有readObject(),触发apache.commons利用链漏洞

25

 

0x03 小结和修复

加密时:序列化转为二进制、encrypt加密、base64加密、放入cookie

解密时:从cookie中取出、base64解密、decrypt解密、反序列化

在利用时,可以根据硬编码在源码中的key构造payload。

修复:升级到最新的shiro、过滤cookie中较长的rememberMe等。CipherKey不要泄露。

 

0x04 结语

通过这次的分析,可以详细了解到shiro反序列化的过程,加深了对利用方式的思考和记忆。除了shiro-550以外的shiro-721则主要是关于AES-128-CBC模式加密的利用,有兴趣可以继续学习。

(完)