扫码领免费网安教程及资料进群!详解Shiro 550反序列化漏洞及环境搭建

扫码领资料

扫码领免费网安教程及资料进群!详解Shiro 550反序列化漏洞及环境搭建

获网安教程

免费&进群

本文由掌控安全学院-holic投稿

0x01、前言

相信大家在面试时经常会问到关于 java 反序列化的问题,也会问到一些标志性的漏洞,例如 shiro 反序列化漏洞,还有 weblogic 反序列化漏洞。

我将为大家讲解这篇文章。不懂的哥哥可以直接背诵,也可以理解一下就可以了。

为什么要选择 shiro 反序列化呢?为什么不讲 weblogic 呢?

我上次参与了金鸡电影节的临时安全工作,具体情况我不便详述。当时内部出现了 shiro 反序列化漏洞。

准确的来说是Shiro

而它也被称为Shiro 550反序列化漏洞。

细品细品...

0x02、环境搭建

下载地址:

环境情况为:使用的是 Tomcat 8.5.27 ,搭配的是 idea 2020.2 ,所使用的 Java 版本是 jdk 1.8 ,同时还用到了 maven 3.6 。

下载后直接打开,然后打开这个 web 文件夹就可以了。其他的自行通过百度来解决,并且还需要导入一些 jstl 的 jar 等。

0x03、漏洞原理

shiro 默认采用了 CookieRememberMeManager 。其对 cookie 的处理流程如下:

获取 rememberMe 的 cookie 值,接着对其进行 Base64 解码,之后再进行 AES 解密,最后进行反序列化。

AES 的密钥是硬编码的,这一情况使得攻击者能够构造恶意数据,从而造成反序列化的 RCE 漏洞。

payload 构造的顺序则就是相对的反着来:

恶意命令被进行序列化操作,接着进行 AES 加密,之后进行 base64 编码,最后发送 cookie

在整个漏洞利用过程里,AES 加密的密钥是比较重要的。这个密钥默认是被硬编码的。因此,如果没有对默认的密钥进行修改,就能够自己生成恶意构造的 cookie 了。

shiro特征:

0x04、漏洞复现

复现文章

0x05、漏洞分析

简单介绍利用:

5.1、加密

那既然我们要分析,那入口点在哪呢?

Shiro 在 1.2.4 版本及以下时,其默认使用的是 CookieRememberMeManager。

我们看看这边的 CookieRememberMeManager 类继承了 AbstractRememberMeManager,然后我们进去看看具体情况。

我们可以看到这边这个类里面有硬编码。

接着它继承了 RememberMeManager 接口;我们接着深入去探究一下究竟是怎么回事。

从名字来看,能知晓这些是关于登陆成功、登陆失败以及退出的一些 service。既然是这样,必然会调用这个登陆成功的接口,接着再去完成这个接口的实现。

所以我们直接在这个接口下个断点,看看是怎么个流程;

从这里可以看出调用了 isRememberMe(),这表明这是在判断用户是否选择了 RememberMe 选项。而我们是进行了勾选的。

如果我们的条件满足,这边的判断会返回 True,然后我们就会进入 this.rememberIdentity(subject, token, info)。

subject 存储着一些登陆相关的信息,像 session 等。而 authcInfo 存储的是用户名。

PrincipalCollection 是一个身份集合。在 Shiro 中可以同时配置多个 Realm,所以身份信息可能会有多个。正因如此,它提供了 PrincipalCollection 来用于聚合这些身份信息。关于具体情况,我们不做详细讲解,也不深入去理解其原理。

然后我们接着在 F7 处继续对 this.rememberIdentity(subject, principals) 进行跟进。

这我们有点懵,将身份信息干嘛?

我们去查看这个 convertPrincipalsToBytes()方法;

看到了 serialize()方法,难道这边开始要进行序列化了吗?还是有其他情况呢?

从这里我们能够得知是跳过了两层,到达了 DefaultSerializer 类的 serialize 方法。

看到这里就懂了,这里先转为byte,写入缓冲区;

首先进行了序列化操作,接着通过 toByteArray()方法将序列化后的内容转化为 Byte 数组并返回。

接着回到原先的位置 convertPrincipalsToBytes()之中。随后进行 if 判断,如果 getCipherService()方法不是空的,就进入该条件内部。

我们f7进去内部看看;

发现又是一个 cipherService,这究竟是什么呢;我们来进行翻译,因为大多数开发人员都会使用简称。

也就是要获取密码服务吗?那是什么样的密码服务呢?我们继续对 F7 进行跟进,结果发现直接就推出了。

我们接着用 Ctrl+左键进入查看。可以看到,这里新建了一个 aes 加密服务。

我们点击 debugger 所在的位置,回到刚才的那个地方。这样我们就不用继续深入进去了,而是可以思考一下,这里是需要获取加密服务的,如果没有获取到,就不进入。

获取到的话,则进入该条件;

直接F8下来,进入,然后我们再手动添加变量监视。

可以发现正如我们所想的,获取aes加密服务;

然后调用加密相关的 encrypt()方法,一般了解英文的人都清楚这个单词的含义是加密。

那我们初步判断这是个加密方法。

我们f7跟进去看看什么情况。

我们能够得知这个参数为 byte[] serialized。这意味着,在这里我们要对刚刚的序列化流的数据进行加密。

然后我们查看了 this.getCipherService(),这里获取到了 aes 加密服务;接着判断它不为空,因为之前上面已经分析过了。然后我们进入条件判断的内部。

ByteSource 是通过 cipherService.encrypt 方法对 serialized 进行加密得到的,使用的加密密钥是 this.getEncryptionCipherKey()。

这里调用 cipherService 的 encrypt()方法,将序列化数据传入其中,同时也传入 getEncryptionCipherKey 方法。

加密过程,我们就应该不怎么感兴趣了;有兴趣的可以自己研究

我们从 getEncryptionCipherKey()这个名字能够得知它是一个用于获取 key 的方法。

那我们f7进入看看

我们再进一步去查看一下。接着发现直接就返回了。哎呀,这怎么跟别人不一样呢。

那我们就不追了

第一步提到硬编码存储在这个位置,构造方法在其下方,并且可以看到这里设置了 key。

我们回到了原来的地方。我们知道这边可以获取加密的 key 就可以了。

接着,利用平台的默认字符集把字符串编码成 byte 序列,接着把这个结果存储到一个新的 byte 数组里。如此,加密部分就完成了。

5.2、解密

此处我无法找到,也回溯不到,这让我很烦恼。最后我想到了我们的加密入口。

既然自动跳到了这里,那么我们就直接在此处下个断点,重新开始

随后我们进入这个方法,即 getRememberedSerializedIdentity(),来看看究竟是什么东西。

此处我们依然还很懵,没事;

一直f8,期间倒是没有什么有意思或者重点的地方;

我们走到这里之后,有一个 this.getCookie().readValue(request, response)的操作,这是用于读取 cookie 中的数据,并且必须要进行跟进。

我们进到了这个 readvalue()方法中,现在先看看是什么情况。

从名字能够得知这是一个用于读取值的方法。读取的是什么值呢?是请求包的值。

通过 getName()方法获取到了 key 为“remeberMe”的内容。接着将其 value 置为空。之后通过 getCookie 操作获取到了 cookie。

最后判断cookie不为空,则进入内部;

接着获取到了 cookie 的值,这个值是序列化的内容。接着又将序列化内容 return 了回来。

随后返回到之前的地方。现在 rememberMe 的值不是 delete,而是序列化内容,所以进入到第二个条件分支。

一直到这一步,将其进行 base64 解码,使其变为二进制数据,然后得到了 decoded 的 byte 数组。

得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化

目前只进行了 Base64 解码,接着还需要 aes 解码。我们会继续跟进此事。

返回到了上层。在这里,我们知道 bytes 是二进制数据,接着我们来看条件判断。如果 bytes 数组不为空并且长度大于 0,就进入里面。因为我们肯定满足这个条件,所以我们先进行两步 f8 操作,然后再进行一步 F7 操作,进入到 convertBytesToPrincipals 中看看是什么。

可以看出接下来我们要逐步实现步骤。首先判断 key 不为空,接着进入内部。

从这里开始,便进入了进行 aes 解密的步骤。我们通过 F7 跟进的方法来进行查看。

这里将恶意的 bytes 数组重新进行赋值给 serialized ,接着再去获取加密服务,即 AES/CBC/PKCS5Padding 。

同时抵达了下一步;开始真正地进行解密了,这里有两个参数,其一为加密的 bytes 数组,其二是获取到的 key,也就是硬编码;我们随即直接进入 decrypt()方法之中

在解密过程中,我对密码学并不擅长。那些复杂的内容让我感到头晕,像涉及到 aes 等的加密解密部分,我就会直接跳过。所以,情况依旧不变,继续跳过!!!

此处返回到了上一层。我们能看出这个 byteSource 是通过 aes 解密得到的序列化流。接着,在默认字符集下,将字符串编码为 byte 序列,然后把结果存储到一个新的 byte 数组 serialized 中。接下来,就差反序列化这一步了。

得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化

我们继续return,返回到上一层

从名字上看就能够明白它是反序列化的方法,我们接着去跟进 deserialize()方法进行查看

看到还有一层,我们继续F7跟进

如果形成了反序列化漏洞,那么没有 readObject()是不可能的。

所以我们看到了最后一道光,就这么愉快的结束了。

0x06、总结

实际上需要学习加密解密的方法,才能够进行 poc 的编写。然而,这里只是对思路有个了解,具体可参考其他文章。

#h2-7

申明:本公众号分享的内容只是用于网络安全技术方面的探讨,绝对不能用于违法的方面。

所有的渗透行为都必须获取授权。如果违反规定,后果需要自行承担,并且这与本号以及作者没有任何关系。请一定要牢记要遵守法律。

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读