Java反序列化DoS攻击通过恶意数据耗尽JVM资源,防御需在反序列化前设限:用JDK 9+ serialFilter控制深度、引用数与数组大小;白名单类加载拦截危险类型;字节流前置校验识别循环引用等特征;最终建议替换为JSON、Protobuf等安全协议。

限制反序列化深度与图结构复杂度
ObjectInputStream本身不校验对象图结构,需靠JDK内置过滤器主动干预。JDK 9+支持`jdk.serialFilter`系统属性,可精准控制递归深度、引用数量和集合大小:启动参数示例:
-Djdk.serialFilter="maxdepth=5;maxrefs=1000;maxarray=100000;java.lang.*;java.util.*;!"
说明:
- maxdepth=5:禁止超过5层嵌套的对象引用,阻断深层链式结构攻击
- maxrefs=1000:限制整个反序列化过程中最多允许1000个对象引用,防循环引用和冗余节点
-
maxarray=100000:单个数组长度上限设为10万,避免
new byte[Integer.MAX_VALUE]类恶意分配 - 末尾
!表示未显式放行的类一律拒绝,防止漏放危险类型
白名单类加载 + 动态拦截非法类型
仅靠通配符或泛型边界无法阻止DoS,必须从类加载源头卡控。继承ObjectInputStream并重写resolveClass(),结合运行时类名检查:
示例代码:
立即学习“Java免费学习笔记(深入)”;
public class SafeObjectInputStream extends ObjectInputStream {
private static final Set<String> ALLOWED_CLASSES = Set.of(
"java.lang.String", "java.util.ArrayList", "java.time.LocalDateTime"
);
protected SafeObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
if (!ALLOWED_CLASSES.contains(className)) {
throw new InvalidClassException("Class not allowed: " + className);
}
return super.resolveClass(desc);
}
}
该方式可杜绝如org.apache.commons.collections4.functors.InvokerTransformer等易被滥用的高开销类加载,同时避免因反射创建大量中间对象引发GC风暴。
前置校验字节流特征,快速拒绝可疑载荷
在调用ObjectInputStream之前,对原始字节流做轻量解析,识别典型DoS模式:
- 扫描前256字节中
TC_REFERENCE标记出现频次,超阈值(如>50次)即判定为循环引用攻击 - 检查序列化头后紧跟的类描述符数量,若>100个且无业务合理场景,直接丢弃
- 统计
TC_ARRAY与TC_BLOCKDATA块总长度,单次请求超5MB视为异常
这种校验不依赖反序列化逻辑,毫秒级完成,适合网关或Filter层统一拦截。
替换原生序列化协议,从根源规避风险
最彻底的DoS防护是不用Java原生序列化。对外暴露接口(如REST API、RPC)优先采用无执行能力的格式:-
JSON(Jackson/FastJSON):默认不还原任意类,只生成POJO或Map/List;禁用
enableDefaultTyping()可防类型混淆 - Protobuf/Avro:强Schema约束,字段数量、嵌套层级、字符串长度均可在IDL中硬性定义
-
自定义二进制协议:如用Kryo(关闭
RegistrationRequired并预注册类)配合长度前缀帧,天然隔离超长负载
内部服务间通信若必须用序列化,也应限定为短生命周期、低复杂度DTO,避免传输含闭包、监听器、动态代理的“活对象”。


















