模块 java.base
 java.io

接口 ObjectInputFilter

函数接口:
这是一个功能接口,因此可以用作 lambda 表达式或方法引用的赋值目标。

@FunctionalInterface public interface ObjectInputFilter
在反序列化期间过滤类、数组长度和图形指标。

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 。请注意,过滤器工厂实现还可以使用其支配的任何上下文信息,例如,从应用程序线程上下文或其调用堆栈中提取的信息,以组成和组合一个新的过滤器。它不限于只使用它的两个参数。

主动反序列化过滤器工厂是:

过滤器

可以从 模式字符串 或基于 类的谓词允许拒绝 类创建过滤器。

过滤器的 checkInput(FilterInfo) 方法在 阅读对象 时被调用零次或多次。调用该方法来验证类、每个数组的长度、从流中读取的对象数、图的深度以及从流中读取的总字节数。

复合过滤器组合或检查其他过滤器的结果。 merge(filter, anotherFilter) 过滤器结合了两个过滤器的状态值。当状态为 UNDECIDED 时,rejectUndecidedClass(filter) 检查类过滤器的结果。在许多情况下,任何不是 ALLOWED 过滤器的类都应该是 REJECTED

反序列化过滤器确定是允许还是拒绝参数,并应返回适当的状态:ALLOWED REJECTED 。如果过滤器无法确定它应该返回的状态 UNDECIDED 。过滤器应针对特定用例和预期类型进行设计。为特定用途设计的过滤器可能会传递过滤器范围之外的类。如果过滤器的目的是拒绝类,那么它可以拒绝匹配的候选类并为其他类报告UNDECIDED。可以使用类等于 nullarrayLength 等于 -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
参见:
  • 方法详情

    • 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 的过滤器。过滤器返回 ALLOWEDotherStatus 基于 non-null 类的谓词和 UNDECIDED 如果类是 null

      当调用过滤器的 checkInput(info) 方法时,谓词应用于 info.serialClass() ,返回 Status 为:

      • UNDECIDED ,如果 serialClassnull
      • ALLOWED ,如果类的谓词返回 true
      • 否则,返回 otherStatus

      例如,创建一个过滤器,允许从平台或引导类加载器加载任何类。

        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 的过滤器。过滤器返回 REJECTEDotherStatus 基于 non-null 类的谓词和 UNDECIDED 如果类是 null 。当调用过滤器的 checkInput(info) 方法时,谓词应用于 serialClass() ,返回状态为:
      • UNDECIDED ,如果 serialClassnull
      • REJECTED ,如果类的谓词返回 true
      • 否则,返回 otherStatus

      例如,创建一个过滤器将拒绝从应用程序类加载器加载的任何类。

        ObjectInputFilter f = rejectFilter(cl ->
           cl.getClassLoader() == ClassLoader.ClassLoader.getSystemClassLoader(), Status.UNDECIDED);
       
      参数:
      predicate - 用于测试非空类的谓词
      otherStatus - 谓词为 false 时使用的状态
      返回:
      如果类上的谓词是 true,则返回一个返回 REJECTED 的过滤器
      自从:
      17
    • merge

      static ObjectInputFilter  merge(ObjectInputFilter  filter, ObjectInputFilter  anotherFilter)
      返回一个过滤器,它合并了一个过滤器和另一个过滤器的状态。如果 another 过滤器是 null ,则返回 filter。否则,将返回一个 filter 以合并这对 non-null 过滤器。返回的过滤器实现了checkInput(FilterInfo) 方法如下:
      • FilterInfo 上调用 filter 以获取其 status
      • 如果 statusREJECTED 则返回 REJECTED
      • 调用 anotherFilter 以获取 otherStatus
      • 如果 otherStatusREJECTED 则返回 REJECTED
      • 返回 ALLOWED ,如果 statusotherStatusALLOWED
      • 否则返回UNDECIDED
      参数:
      filter - 过滤器
      anotherFilter - 要与过滤器合并的过滤器,可能是 null
      返回:
      一个 ObjectInputFilter 合并过滤器的状态和另一个过滤器
      自从:
      17
    • rejectUndecidedClass

      static ObjectInputFilter  rejectUndecidedClass(ObjectInputFilter  filter)
      返回一个过滤器,该过滤器调用给定的过滤器并将类的 UNDECIDED 映射到 REJECTED,有一些特殊情况,否则返回状态。如果该类不是原始类也不是数组,则返回的状态为 REJECTED 。如果该类是原始类或数组类,则执行额外的检查;有关详细信息,请参阅下面的列表。

      如果过滤器返回 UNDECIDED ,则对象反序列化接受一个类。添加过滤器以拒绝未被允许或拒绝的类的未定结果,可以防止类漏过过滤器。

      实现要求:
      返回的过滤器实现了checkInput(FilterInfo) 方法如下:
      • 调用 FilterInfo 上的过滤器以获取其 status
      • 如果状态为 REJECTEDALLOWED 则返回 status
      • 如果 filterInfo.getSerialClass() serialClassnull,则返回 UNDECIDED
      • 如果类不是 array,则返回 REJECTED
      • 如果 serialClassarray ,则确定基本组件类型;
      • 如果基本组件类型是 原始阶级,则返回 UNDECIDED
      • 调用 base component type 上的过滤器以获取其 component status
      • 如果组件状态为 ALLOWED 则返回 ALLOWED
      • 否则,返回 REJECTED
      参数:
      filter - 过滤器
      返回:
      ObjectInputFilter ObjectInputFilter.Status.UNDECIDED 状态映射到 ObjectInputFilter.Status.REJECTED 的类,否则返回过滤器状态
      自从:
      17