JEP 290:过滤传入的序列化数据
概述
允许对对象序列化数据的传入流进行过滤,以提高安全性和鲁棒性。
目标
-
提供一种灵活的机制,将可反序列化的类从应用程序可用的任何类缩小到与上下文相适应的类集合。
-
在反序列化期间为过滤器提供图形大小和复杂性的度量标准,以验证正常的图形行为。
-
提供一种机制,用于 RMI 导出的对象以验证调用中预期的类。
-
过滤器机制不得要求对 ObjectInputStream 的现有子类进行子类化或修改。
-
定义一个可以通过属性或配置文件配置的全局过滤器。
非目标
-
定义或维护任何特定的策略来规定哪些类应被允许或禁止。
-
修复特定类或实现的复杂性或完整性问题。
-
在流中提供前瞻性能力。
-
提供对对象内容的细粒度可见性。
成功指标
- 对类的简单拒绝列表性能影响最小可测量
动机
安全指南始终要求在使用之前验证来自外部来源的输入。过滤机制将允许对象序列化的客户端更容易地验证其输入,并允许导出的 RMI 对象验证调用参数。
描述
核心机制是一个由序列化客户端实现的过滤器接口,并设置在 ObjectInputStream
上。在反序列化过程中,会调用过滤器接口的方法,以验证正在反序列化的类、所创建数组的大小,以及描述流长度、流深度和引用数量等指标(当流被解码时)。过滤器会返回一个状态,以接受、拒绝或保持状态未定。
对于流中的每个新对象,在对象被实例化和反序列化之前,会使用该对象的类调用过滤器。对于具体编码在流中的基本类型或 java.lang.String
实例,不会调用过滤器。对于每个数组,无论是基本类型的数组、字符串数组还是对象数组,都会使用数组类和数组长度调用过滤器。对于从流中已经读取的对象的每个引用,都会调用过滤器,以便它可以检查深度、引用数量和流长度。如果启用了日志记录,过滤器操作会被记录到 java.io.serialization
日志记录器中。
对于 RMI,对象通过 UnicastServerRef
导出,该对象在 MarshalInputStream
上设置过滤器,以在反序列化时验证调用参数。通过 UnicastRemoteObject 导出对象应该支持设置用于反序列化的过滤器。
全局过滤器
通过系统属性或配置文件来配置整个进程范围的过滤器。如果提供了系统属性,它将取代安全属性值。
- 系统属性
jdk.serialFilter
- 配置文件
conf/security/java.security
中的安全属性jdk.serialFilter
过滤器被配置为一系列模式,每个模式要么与流中的类名匹配,要么作为限制。模式之间用“;”(分号)分隔。空格是重要的,并被认为是模式的一部分。
限制模式包含一个“=”并设置限制。如果某个限制出现多次,则使用最后一个值。如果调用 ObjectInputFilter.checkInput(...)
中的任何值超过了相应的限制,过滤器将返回 Status.REJECTED。无论模式序列中的顺序如何,都会在类之前检查限制。
maxdepth=value
— 图形的最大深度maxrefs=value
— 内部引用的最大数量maxbytes=value
— 输入流中允许的最大字节数maxarray=value
— 允许的最大数组大小
其他模式从左至右匹配从 Class::getName
返回的类或包名。如果该类是数组类型,则要匹配的类或包为元素类型。任意维度的数组均被视为与元素类型相同。例如,模式 "!example.Foo
" 会拒绝创建 example.Foo
的任何实例或数组。
- 如果模式以 "
!
" 开头,则当模式的其余部分匹配时类会被拒绝,否则会被接受。 - 如果模式包含 "/",则直到 "/" 的非空前缀为模块名称。如果模块名称与类的模块名称匹配,则剩余的模式将与类名称进行匹配。如果没有 "/",则不会比较模块名称。
- 如果模式以 "
.**
" 结尾,则它会匹配包及其所有子包中的任何类。 - 如果模式以 "
.*
" 结尾,则它会匹配包中的任何类。 - 如果模式以 "
*
" 结尾,则它会匹配任何以该模式为前缀的类。 - 如果模式等于类名称,则匹配成功。
- 否则,状态未定。
ObjectInputFilter 接口和 API
对象输入过滤器接口由 RMI 和序列化的客户端实现,并提供了进程范围可配置过滤器的行为。
interface ObjectInputFilter {
Status checkInput(FilterInput filterInfo);
enum Status {
UNDECIDED,
ALLOWED,
REJECTED;
}
interface FilterInfo {
Class<?> serialClass();
long arrayLength();
long depth();
long references();
long streamBytes();
}
public static class Config {
public static void setSerialFilter(ObjectInputFilter filter);
public static ObjectInputFilter getSerialFilter(ObjectInputFilter filter) ;
public static ObjectInputFilter createFilter(String patterns);
}
}
ObjectInputStream 过滤器
ObjectInputStream
还有其他方法来设置和获取当前的过滤器。如果未为 ObjectInputStream
设置过滤器,则会使用全局过滤器(如果有)。
public class ObjectInputStream ... {
public final void setObjectInputFilter(ObjectInputFilter filter);
public final ObjectInputFilter getObjectInputFilter(ObjectInputFilter filter);
}
替代方案
修改现有的子类和方法,但这就需要进行一些更改,而这会阻碍在第三方实现中的使用。
测试
无需更新现有的测试。新的单元测试将会测试过滤机制与序列化流、RMI 导出的对象以及全局过滤机制。
风险与假设
提供给过滤器的指标(支持拒绝列表、接受列表和流指标)应该足够了。当应用于已知用例时,可能会发现一些额外的过滤机制。
JDK 9 将引入新的 API 和接口。将此功能反向移植到以前的版本需要引入特定于实现的 API,以避免对旧版本的 Java SE 规范进行更改。