反序列化绕过构造方法导致安全校验失效,因其直接分配内存填充字段;应禁用不可信输入反序列化、启用JVM过滤器、改用JSON等安全序列化方案,并通过枚举单例、readObject校验等方式加固。

反序列化本身不直接修改文件字节码,但攻击者可通过篡改序列化数据(如 Base64 编码后的字节流)绕过构造方法的安全检查——因为 Java 反序列化跳过构造函数,直接分配内存并填充字段,导致像单例校验、参数合法性判断等逻辑被完全绕过。
理解绕过构造方法的本质
Java 原生反序列化使用 ObjectInputStream.readObject() 时,JVM 会:
- 跳过所有构造方法(包括私有构造器和带参构造器)
- 直接调用
Unsafe.allocateInstance()创建对象实例 - 再逐个还原字段值(含 private、final 字段),不受访问修饰符限制
这意味着即使你在构造函数里写了 if (instance != null) throw new IllegalStateException(),反序列化仍能生成第二个实例,单例被破坏;同理,字段校验、初始化逻辑全部失效。
阻止字节流篡改与非法反序列化入口
重点不是“防止字节码被改”,而是切断恶意字节流进入反序列化流程的路径:
立即学习“Java免费学习笔记(深入)”;
-
禁用不可信输入的反序列化:任何来自 HTTP 请求头/体、Cookie(如 rememberMe)、Redis、MQ、RMI、JMX 的二进制数据,只要未经强验证,一律禁止走
ObjectInputStream -
启用 JVM 全局过滤器(JDK 9+):启动时加参数
-Djdk.serialFilter=java.base/*;com.yourpackage.**;!*,显式放行基础类和你自己的 DTO,末尾!*拦截一切未声明类 -
对 JDK 8 或老系统补丁式防护:继承
ObjectInputStream,重写resolveClass()做白名单校验(注意:仅校验类名,不防 gadget 链;需配合后续步骤)
用替代方案彻底规避原生序列化风险
最有效的办法是不用 Serializable:
-
改用 JSON/YAML 序列化:如 Jackson 或 Gson,它们只解析字段值、不执行任意代码,且天然不支持
readObject()这类危险钩子 -
若必须保留二进制兼容性:使用 Kryo、FST 等库,并关闭其注册机制(
setRegistrationRequired(true)),只允许预注册的安全类 -
敏感对象禁用序列化:给关键类加
private void readObject(ObjectInputStream in) throws IOException { throw new InvalidClassException("Not allowed"); }
加固构造逻辑使其抗反序列化绕过
即便反序列化发生,也能让对象处于无效或自毁状态:
-
在
readObject()中主动校验:比如检查 final 字段是否被篡改、是否满足业务约束,不满足则抛异常或清空关键字段 -
用枚举实现单例:
public enum Singleton { INSTANCE; }—— JVM 保证枚举反序列化仍为同一实例,反射和反序列化均无法构造新实例 -
运行时校验 + SecurityManager(辅助):在反序列化后立即调用
SecurityManager.checkPermission()或自定义校验方法,阻断非法状态对象继续参与业务流程


















