如何配置Java反序列化过滤器防止利用基本类型默认值进行参数注入
Java反序列化中基本类型默认值被滥用可导致逻辑绕过,攻击者利用JVM自动填充int为0、boolean为false的特性绕过校验。防御措施包括:启用并配置ObjectInputFilter限制类加载;用包装类型替代基本类型并做非空校验;重写readObject()强制字段完整性检查。
说起Ja va反序列化漏洞,大家第一反应往往是那些依赖gadget chain的“重武器”,比如FastJson、CommonsCollections之类的经典组合。但有一个很容易被忽视的“小坑”,它不依赖任何链条,却同样能造成严重的逻辑绕过——那就是基本类型的默认值滥用问题。今天我们就来专门聊聊这个,以及到底该怎么防。
从根上说,问题出在JVM反序列化时的一个“偷懒”行为:对于基本类型字段(比如 int、boolean),如果序列化流里没有给它们赋值,JVM就自动填上默认值——int 是0,boolean 是 false。攻击者正是利用这一点,构造恶意序列化数据,让那些本应被显式设置的字段“漏网”,从而绕过业务层的关键校验。
理解漏洞根源:默认值不是“注入”,而是“未赋值”被误用
关键点在于:JVM在反序列化时,对基本类型字段压根不做校验,直接填上默认值。而业务代码如果写得不够严谨,就会掉进这个坑里。举个最典型的例子:
if (user.isAdmin == false) { // 若未反序列化赋值,此处为 false,误判为普通用户
denyAccess();
}
注意,这里并不是攻击者“注入”了一个 false,而是字段根本没被设值,JVM顺手给了一个默认值。因此,单纯靠“过滤输入”是防不住的,必须从设计和校验层面把逻辑缺口堵死。
启用并严格配置 ObjectInputFilter(JDK 9+)
虽然ObjectInputFilter拦不住基本类型的默认值问题,但它依然是一道重要的基础防线——可以防止恶意类加载和gadget利用。不过要记住,默认策略是“允许一切”,所以必须显式配置:
- 全局设置(推荐在应用启动时一次配置):
System.setProperty("jdk.serialFilter", "maxdepth=5;maxarray=100000;package.whitelist.*;!*"); - 对每个
ObjectInputStream实例单独设置更安全:
ois.setObjectInputFilter(ObjectInputFilter.Config.createFilter("my.app.model.**;!*")); - 务必禁用通配符泛滥(如
ja va.**)、限制深度与数组大小,避免DOS或gadget加载。
话说回来,这才只是第一步。
用包装类型替代基本类型,并做显式 null 检查
这才是针对默认值滥用最直接、最有效的编码改进:
- 把
boolean isAdmin改成Boolean isAdmin,int age改成Integer age - 反序列化后,立刻检查关键字段是否为
null:
if (user.isAdmin == null) throw new InvalidObjectException("isAdmin must be explicitly set"); - 如果配合Jackson/Gson等JSON库,可以开启
DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES,但注意这条只对JSON生效,对原生Ja va序列化无效。
这一步改动看似简单,却能从本质上切断默认值带来的逻辑风险。
重写 readObject() 并强制校验字段完整性
如果想要更精细的控制,可以在可序列化类中自定义 readObject 方法:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 先完成默认反序列化
if (this.isAdmin == null) {
throw new InvalidObjectException("Field 'isAdmin' is missing or null");
}
if (this.age == null || this.age <= 0 || this.age > 150) {
throw new InvalidObjectException("Invalid 'age' value");
}
}
这个方法的妙处在于:每次反序列化都会自动执行一次完整性校验,确保所有关键字段都被显式设置,并且符合业务约束。这样一来,基本类型的默认值就再也钻不了空子了。
总结一下:基本类型默认值滥用不是那种需要“找链条、拼payload”的漏洞,但它的危害一点不小——因为它绕的是业务逻辑。防御的关键,不是依赖过滤器,而是从编码规范和设计层面下手,把“未赋值”这条路彻底堵死。
