- 函数接口:
- 这是一个功能接口,因此可以用作 lambda 表达式或方法引用的赋值目标。
Warning: Deserialization of untrusted data is inherently dangerous and should be avoided. Untrusted data should be carefully validated according to the "Serialization and Deserialization" section of the Secure Coding Guidelines for Java SE. Serialization Filtering describes best practices for defensive use of serial filters.
为了防止反序列化漏洞,应用程序开发人员需要清楚地描述每个组件或库可以反序列化的对象。对于每个上下文和用例,开发人员应该构建并应用适当的过滤器。
反序列化过滤工厂和过滤器
反序列化过滤的部分是过滤器、复合过滤器和过滤器工厂。每个过滤器都对类和资源限制执行检查以确定状态为拒绝、允许或未决定。过滤器可以由其他过滤器组成,并合并或组合它们的结果。过滤器工厂负责为每个ObjectInputStream
建立和更新过滤器。
对于简单的情况,可以为整个应用程序设置静态 JVM 范围的过滤器,而无需设置过滤器工厂。可以使用命令行上的系统属性或调用 Config.setSerialFilter 来设置 JVM 范围的过滤器。无需指定自定义过滤器工厂,默认为内置过滤器工厂。内置过滤器工厂为每个 ObjectInputStream 提供 静态 JVM 范围的过滤器。
例如,允许示例类、允许 java.base
模块中的类并拒绝所有其他类的过滤器可以设置为:作为命令行属性:
% java -Djdk.serialFilter="example.*;java.base/*;!*" ...
var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*")
ObjectInputFilter.Config.setSerialFilter(filter);
在具有多个执行上下文的应用程序中,应用程序可以提供一个过滤厂通过为每个上下文提供自定义过滤器来保护各个上下文。构造流时,将调用过滤器工厂以从可用信息中识别执行上下文,包括当前线程本地状态、调用者层次结构、库、模块和类加载器。此时,用于创建或选择过滤器的过滤器工厂策略可以根据上下文选择特定过滤器或过滤器组合。 JVM 范围的反序列化过滤器工厂确保可以在每个 ObjectInputStream
上设置特定于上下文的反序列化过滤器,并且可以检查从流中读取的每个对象。
调用过滤器工厂
JVM 范围的过滤器工厂是一个函数,当每个 ObjectInputStream
是 建 和 特定于流的过滤器已设置 时调用。参数是当前过滤器和请求的过滤器,它返回要用于流的过滤器。从 ObjectInputStream 构造函数 调用时,第一个参数是 null
,第二个参数是 静态 JVM 范围的过滤器。从 ObjectInputStream.setObjectInputFilter
调用时,第一个参数是当前在流上设置的过滤器(在构造函数中设置),第二个参数是给 ObjectInputStream.setObjectInputFilter
的过滤器。当前和新的过滤器可能都是 null
并且工厂可能返回 null
。请注意,过滤器工厂实现还可以使用其支配的任何上下文信息,例如,从应用程序线程上下文或其调用堆栈中提取的信息,以组成和组合一个新的过滤器。它不限于只使用它的两个参数。
主动反序列化过滤器工厂是:
- 通过
ObjectInputFilter.Config.setSerialFilterFactory(BinaryOperator)
或系统属性jdk.serialFilterFactory
或安全属性jdk.serialFilterFactory
设置的应用程序特定过滤器工厂。 - 否则,内置反序列化过滤器工厂在从 ObjectInputStream 构造函数 调用时提供 静态 JVM 范围的过滤器,并在从
ObjectInputStream.setObjectInputFilter(ObjectInputFilter)
调用时替换静态过滤器。参见 getSerialFilterFactory。
过滤器
可以从 模式字符串 或基于 类的谓词 到 允许 或 拒绝 类创建过滤器。过滤器的 checkInput(FilterInfo)
方法在 阅读对象 时被调用零次或多次。调用该方法来验证类、每个数组的长度、从流中读取的对象数、图的深度以及从流中读取的总字节数。
复合过滤器组合或检查其他过滤器的结果。 merge(filter, anotherFilter)
过滤器结合了两个过滤器的状态值。当状态为 UNDECIDED
时,rejectUndecidedClass(filter)
检查类过滤器的结果。在许多情况下,任何不是 ALLOWED
过滤器的类都应该是 REJECTED
。
反序列化过滤器确定是允许还是拒绝参数,并应返回适当的状态:ALLOWED
或 REJECTED
。如果过滤器无法确定它应该返回的状态 UNDECIDED
。过滤器应针对特定用例和预期类型进行设计。为特定用途设计的过滤器可能会传递过滤器范围之外的类。如果过滤器的目的是拒绝类,那么它可以拒绝匹配的候选类并为其他类报告UNDECIDED
。可以使用类等于 null
、arrayLength
等于 -1、深度、引用数和流大小来调用过滤器,并返回仅反映一个或仅反映其中一些值的状态。这允许过滤器特定于它正在报告的选择,并使用其他过滤器而不强制允许或拒绝状态。
过滤器模型示例
对于简单的应用程序,单个预定义过滤器列表允许或拒绝的类可能足以管理反序列化意外类的风险。对于由多个模块或库组成的应用程序,应用程序的结构可用于标识在应用程序的每个上下文中每个 ObjectInputStream
允许或拒绝的类。反序列化过滤器工厂在构造每个流时被调用,并且可以检查线程或程序以确定要应用的特定于上下文的过滤器。一些可能的例子:
- 线程本地状态可以保存要应用的过滤器或与特定于流的过滤器组合。可以从应用程序或库维护的虚拟过滤器堆栈中推送和弹出过滤器。
- 过滤器工厂可以识别反序列化方法的调用者,并使用模块或库上下文来选择过滤器或编写适当的特定于上下文的过滤器。一种机制可以识别对序列化类具有受限或不受限访问权限的被调用方,并相应地选择过滤器。
过滤线程中每个反序列化的示例
此类展示了应用程序提供的过滤器工厂如何组合过滤器来检查线程中发生的每个反序列化操作。它定义了一个线程局部变量来保存特定于线程的过滤器,并构建了一个过滤器工厂,该工厂将过滤器与静态 JVM 范围的过滤器和特定于流的过滤器组合在一起,拒绝这两个过滤器未处理的任何类。如果设置了流特定过滤器并且不接受或拒绝类,则应用组合的 JVM 范围过滤器和线程过滤器。doWithSerialFilter
方法设置线程特定过滤器并调用提供的应用程序 Runnable
。
public static final class FilterInThread implements BinaryOperator<ObjectInputFilter> {
private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();
// Construct a FilterInThread deserialization filter factory.
public FilterInThread() {}
// Returns a composite filter of the static JVM-wide filter, a thread-specific filter,
// and the stream-specific filter.
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
if (curr == null) {
// Called from the OIS constructor or perhaps OIS.setObjectInputFilter with no current filter
var filter = filterThreadLocal.get();
if (filter != null) {
// Merge to invoke the thread local filter and then the JVM-wide filter (if any)
filter = ObjectInputFilter.merge(filter, next);
return ObjectInputFilter.rejectUndecidedClass(filter);
}
return (next == null) ? null : ObjectInputFilter.rejectUndecidedClass(next);
} else {
// Called from OIS.setObjectInputFilter with a current filter and a stream-specific filter.
// The curr filter already incorporates the thread filter and static JVM-wide filter
// and rejection of undecided classes
// If there is a stream-specific filter merge to invoke it and then the current filter.
if (next != null) {
return ObjectInputFilter.merge(next, curr);
}
return curr;
}
}
// Applies the filter to the thread and invokes the runnable.
public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
var prevFilter = filterThreadLocal.get();
try {
filterThreadLocal.set(filter);
runnable.run();
} finally {
filterThreadLocal.set(prevFilter);
}
}
}
使用过滤器工厂
要使用FilterInThread
实用程序,请创建一个实例并将其配置为 JVM 范围的过滤器工厂。 doWithSerialFilter
方法使用允许示例应用程序和核心类的过滤器调用:
// Create a FilterInThread filter factory and set
var filterInThread = new FilterInThread();
ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);
// Create a filter to allow example.* classes and reject all others
var filter = ObjectInputFilter.Config.createFilter("example.*;java.base/*;!*");
filterInThread.doWithSerialFilter(filter, () -> {
byte[] bytes = ...;
var o = deserializeObject(bytes);
});
除非另有说明,否则将 null
参数传递给此接口及其嵌套类中的方法将导致抛出 NullPointerException
。
- 自从:
- 9
- 参见:
-
内部类总结
内部类修饰符和类型接口描述static final class
一个实用程序类,用于设置和获取 JVM 范围的反序列化过滤器工厂、静态 JVM 范围的过滤器,或从模式字符串创建过滤器。static interface
FilterInfo 提供对有关正在反序列化的当前对象和ObjectInputStream
状态的信息的访问。static enum
检查类、数组长度、引用数、深度和流大小的状态。 -
方法总结
修饰符和类型方法描述static ObjectInputFilter
allowFilter
(Predicate<Class<?>> predicate, ObjectInputFilter.Status otherStatus) 如果类的谓词是true
,则返回一个返回Status.ALLOWED
的过滤器。checkInput
(ObjectInputFilter.FilterInfo filterInfo) 检查类、数组长度、对象引用数、深度、流大小和其他可用的过滤信息。static ObjectInputFilter
merge
(ObjectInputFilter filter, ObjectInputFilter anotherFilter) 返回一个过滤器,它合并了一个过滤器和另一个过滤器的状态。static ObjectInputFilter
rejectFilter
(Predicate<Class<?>> predicate, ObjectInputFilter.Status otherStatus) 如果类上的谓词是true
,则返回一个返回Status.REJECTED
的过滤器。static ObjectInputFilter
返回一个过滤器,该过滤器调用给定的过滤器并将类的UNDECIDED
映射到REJECTED
,有一些特殊情况,否则返回状态。
-
方法详情
-
checkInput
检查类、数组长度、对象引用数、深度、流大小和其他可用的过滤信息。此方法的实现检查在反序列化期间创建的对象图的内容。过滤器返回Status.ALLOWED
、Status.REJECTED
或Status.UNDECIDED
。如果
filterInfo.serialClass()
是non-null
,则有一个类要检查。如果serialClass()
是null
,则没有类,信息仅包含与反序列化图的深度、引用数和读取的流大小相关的指标。- API 注意:
-
每个实现
checkInput
的过滤器都应返回ObjectInputFilter.Status
的值之一。返回null
可能会导致NullPointerException
或其他不可预测的行为。 - 参数:
filterInfo
- 提供有关当前反序列化对象的信息(如果有)以及ObjectInputStream
的状态- 返回:
Status.ALLOWED
如果被接受,Status.REJECTED
如果被拒绝,Status.UNDECIDED
如果未决定。
-
allowFilter
static ObjectInputFilter allowFilter(Predicate <Class <?>> predicate, ObjectInputFilter.Status otherStatus) 如果类的谓词是true
,则返回一个返回Status.ALLOWED
的过滤器。过滤器返回ALLOWED
或otherStatus
基于non-null
类的谓词和UNDECIDED
如果类是null
。当调用过滤器的
checkInput(info)
方法时,谓词应用于info.serialClass()
,返回 Status 为:例如,创建一个过滤器,允许从平台或引导类加载器加载任何类。
ObjectInputFilter f = allowFilter(cl -> cl.getClassLoader() == ClassLoader.getPlatformClassLoader() || cl.getClassLoader() == null, Status.UNDECIDED);
- 参数:
predicate
- 用于测试非空类的谓词otherStatus
- 谓词为false
时使用的状态- 返回:
-
如果类的谓词是
true
,则返回ALLOWED
的过滤器 - 自从:
- 17
-
rejectFilter
static ObjectInputFilter rejectFilter(Predicate <Class <?>> predicate, ObjectInputFilter.Status otherStatus) 如果类上的谓词是true
,则返回一个返回Status.REJECTED
的过滤器。过滤器返回REJECTED
或otherStatus
基于non-null
类的谓词和UNDECIDED
如果类是null
。当调用过滤器的checkInput(info)
方法时,谓词应用于serialClass()
,返回状态为:例如,创建一个过滤器将拒绝从应用程序类加载器加载的任何类。
ObjectInputFilter f = rejectFilter(cl -> cl.getClassLoader() == ClassLoader.ClassLoader.getSystemClassLoader(), Status.UNDECIDED);
- 参数:
predicate
- 用于测试非空类的谓词otherStatus
- 谓词为false
时使用的状态- 返回:
-
如果类上的谓词是
true
,则返回一个返回REJECTED
的过滤器 - 自从:
- 17
-
merge
返回一个过滤器,它合并了一个过滤器和另一个过滤器的状态。如果another
过滤器是null
,则返回filter
。否则,将返回一个filter
以合并这对non-null
过滤器。返回的过滤器实现了checkInput(FilterInfo)
方法如下:- 在
FilterInfo
上调用filter
以获取其status
; - 如果
status
是REJECTED
则返回REJECTED
; - 调用
anotherFilter
以获取otherStatus
; - 如果
otherStatus
是REJECTED
则返回REJECTED
; - 返回
ALLOWED
,如果status
或otherStatus
是ALLOWED
, - 否则返回
UNDECIDED
- 参数:
filter
- 过滤器anotherFilter
- 要与过滤器合并的过滤器,可能是null
- 返回:
-
一个
ObjectInputFilter
合并过滤器的状态和另一个过滤器 - 自从:
- 17
- 在
-
rejectUndecidedClass
返回一个过滤器,该过滤器调用给定的过滤器并将类的UNDECIDED
映射到REJECTED
,有一些特殊情况,否则返回状态。如果该类不是原始类也不是数组,则返回的状态为REJECTED
。如果该类是原始类或数组类,则执行额外的检查;有关详细信息,请参阅下面的列表。如果过滤器返回
UNDECIDED
,则对象反序列化接受一个类。添加过滤器以拒绝未被允许或拒绝的类的未定结果,可以防止类漏过过滤器。- 实现要求:
-
返回的过滤器实现了
checkInput(FilterInfo)
方法如下:- 调用
FilterInfo
上的过滤器以获取其status
; - 如果状态为
REJECTED
或ALLOWED
则返回status
; - 如果
filterInfo.getSerialClass() serialClass
是null
,则返回UNDECIDED
; - 如果类不是 array,则返回
REJECTED
; - 如果
serialClass
是 array ,则确定基本组件类型; - 如果基本组件类型是 原始阶级,则返回
UNDECIDED
; - 调用
base component type
上的过滤器以获取其component status
; - 如果组件状态为
ALLOWED
则返回ALLOWED
; - 否则,返回
REJECTED
。
- 调用
- 参数:
filter
- 过滤器- 返回:
ObjectInputFilter
将ObjectInputFilter.Status.UNDECIDED
状态映射到ObjectInputFilter.Status.REJECTED
的类,否则返回过滤器状态- 自从:
- 17
-