原文链接:2 Serialization Filtering
来源:Java官方文档
译者的话
由于译者的英文水平和编程水平都不高,不理解原文中的一些概念,一些句子也不知道如何翻译。对不知如何翻译的内容,译者使用了机器翻译,并在译文后面的括号中提供了原文。翻译如有错误,请参阅原文。有任何建议或意见,欢迎评论。
由于译者的疏忽,翻译的文档不是最新版,本文翻译的Java官方文档的大版本号是15。最新版比15版文档新增了一些内容,感兴趣的读者可以自己去看看。目前最新文档的链接是Serialization Filtering (oracle.com)。本段以下是正文内容。
可以使用Java序列化过滤机制帮助避免反序列漏洞。有两种过滤器:基于模式的过滤器和自定义过滤器。
主题:
解决反序列化漏洞
应用程序接受并反序列化不受信任的数据很容易受到攻击。创建过滤器可以在反序列化对象之前筛选传入的序列化对象流。
当一个对象的状态被转换为字节流时,它就被序列化了。流可以发送到一个文件、一个数据库或者通过网络传输。如果一个Java对象的类或超类实现了java.io.Serializable接口或java.io.Externalizable子接口,它就可以被序列化。在JDK中,序列化用于许多场合,例如远程方法调用(RMI),用于进程间通信(IPC)协议(如Spring HTTP调用器)的自定义RMI、Java管理扩展(JMX)和Java消息传递服务(JMS)。
当一个对象从序列化形式转换为该对象的副本时,它就被反序列化了。确保转换安全是重要的。反序列化时要执行代码,因为被反序列化的类的readObject方法可以包含自定义代码。可序列化的类,也称为“小工具类”,可以执行任意反射操作,比如创建类和调用类上的方法。如果的应用程序反序列化这些类,它们可能导致拒绝服务或远程代码执行。
可以在过滤器中指定应用程序接受或拒绝的类。也可以控制反序列化时的对象图尺寸和复杂性,以便对象的图不超过合理限制。(You can control the object graph size and complexity during deserialization so that the object graph doesn’t exceed reasonable limits.)(上句中的“graph”及以后出现“graph”均不知含义,为方便查找和修正错误,本文一律翻译为“图”)有两种方式实现过滤器:配置属性和代码。
除了使用过滤器,也可以用下面的方法防止他人利用反序列化漏洞:
readObject方法检查对象不变量。(Validate field values before assignment, including checking object invariants by using the readObject method.)注意:有可用于RMI的内置过滤器。但是,应该仅仅把这些内置过滤器当作起点。配置黑名单并且/或扩展白名单可以增强的应用使用RMI时的安全性。请参阅内置过滤器。
要了解更多关于这些策略的信息,请参阅Java SE安全编程指南的“序列化和反序列化”小节。
Java序列化过滤器
Java序列化过滤机制筛选传入的序列化对象流,这可以增强程序安全性的健壮性。过滤器在传入的类反序列化时执行检查。
下列是JEP 290提出的Java序列化过滤机制的目标:
下面是实现序列化过滤器的方式:
ObjectInputFilter API实现。它们比基于模式的过滤器的控制更加精细,因为它们可以为每个ObjectInputStream定制。自定义过滤器可以设置在单个输入流上或进程中的所有流上。(Custom filters are set on an individual input stream or on all streams in a process.)流中的每个新对象都会调用过滤机制。如果有多个激活的过滤器(进程级过滤器(process-wide filter)、应用过滤器或特定流过滤器)存在,只会调用最贴近当前场景的过滤器。
在大多数情况下,自定义过滤器应该检查是否设置了进程级过滤器。如果有进程级过滤器,除非它的结果是UNDECIDED,否则自定义过滤器应该调用进程级过滤器并且使用它的结果。
从JDK 9开始,以及从8u121、7u131和6u141开始的Java CPU版本都支持序列化过滤器。
白名单和黑名单
在基于模式的过滤器和自定义过滤器都可以使用白名单和黑名单。黑/白名单能用主动或防御性的方法保护应用。
主动方法是使用白名单允许被识别的和受信任的类通过。可以在开发应用时在代码中实现白名单,或者在后面使用基于模式的过滤器实现白名单。这种方法适合只需要处理较少类的应用。通过指定允许的类、包或模块来实现白名单。
创建基于模式的过滤器
使用基于模式的过滤器时用修改应用代码。可以在配置文件中添加进程级过滤器,或者在java命令行中添加特定应用过滤器。
基于模式的过滤器由一系列模式组成。每个模式根据类名或资源限制判断是否接受流中的对象。(Each pattern is matched against the name of a class in the stream or a resource limit.)基于类的模式和资源限制模式可以写在一个过滤器字符串中,每个模式用分号(;)分隔。
基于模式的过滤器语法
由模式组成的过滤器应遵循以下指南:
pattern1.*;pattern2.*
!开头的模式,它将被拒绝。如果一个类匹配的模式开头没有!,它将被接受。下面的过滤器拒绝了pattern1.MyClass但是接受了pattern2.MyClass:!pattern1.*;pattern2.*
*)表示任意类,下面是一些示例:*匹配任意类mypackage.*匹配mypackage中的所有类mypackage.**匹配mypackage和它的子包中的有所类text*匹配任意以text开头的类没有匹配任何过滤器的类将被接受。如果只想接受特定类,过滤器必须拒绝任何不匹配的东西。在类过滤器的最后使用“!*”可以拒绝所有未指定的类。
可以在conf/security/java.security文件或JEP 290查看全部模式语法。
基于模式的过滤器限制
基于模式的过滤器仅能做到简单的接受和拒绝。这些过滤器有一些限制。例如:
为单个应用定义基于模式的过滤器
可以为单个应用程序将基于模式的过滤器定义成系统属性。系统属性将会覆盖安全属性(Security Property)的值。(A system property supersedes a Security Property value.)
要创建一个只应用在单次Java调用的应用程序的过滤器,只需要在命令行中定义jdk.serialFilter系统属性。
下面是一个限制单个应用程序资源使用量的示例:
java -Djdk.serialFilter=maxarray=100000;maxdepth=20;maxrefs=500 com.example.test.Application
为进程中的所有应用程序定义基于模式的过滤器(Define a Pattern-Based Filter for All Applications in a Process)
可以在安全属性定义一个基于模式的过滤器,该过滤器是为进程中所有应用定义的。系统属性将会覆盖安全属性(Security Property)的值。
java.security属性文件:
$JAVA_HOME/conf/security/java.security$JAVA_HOME/lib/security/java.securityjdk.serialFilter安全属性里。定义类过滤器
可以创建一个全局的基于模式的类过滤器。例如,一个使用通配符的类名或包名的模式。
在下例中,过滤器拒绝了一个包中的一个类(!example.somepackage.SomeClass),并且接受该包中的所有其他类:
jdk.serialFilter=!example.somepackage.SomeClass;example.somepackage.*;
上例中的过滤器除了example.somepackage.*中的类,接受所有其他类。要拒绝其他所有类,在末尾加上“!*”:
jdk.serialFilter=!example.somepackage.SomeClass;example.somepackage.*;!*
定义资源限制过滤器
资源过滤器限制图的复杂性和尺寸。可以用下面的参数创建过滤器限制每个应用程序的资源使用量:
maxarray=100000;maxdepth=20;maxrefs=500;maxbytes=500000;创建自定义过滤器
自定义过滤器是在应用程序代码中定义的过滤器。自定义过滤器可以设置单个流上或一个进程中的所有流。可以将自定义过滤器实现为一个模式、一个方法、一个lambda表达式或一个类。
读取序列化对象流
可以在一个ObjectInputStream上设置一个自定义过滤器,或者通过设置进程级过滤器将相同的过滤器设置到每一个流上。(You can set a custom filter on one ObjectInputStream, or, to apply the same filter to every stream, set a process-wide filter.)。如果一个ObjectInputStream上没有过滤器,它将会使用进程级过滤器(如果有的话)。
解码流时,执行下列操作:
java.lang.String实例。(The filter is not called for primitives or for java.lang.String instances that are encoded concretely in the stream.)如果一个对象没被过滤器拒绝,就会被接受。
在单个流上设置自定义过滤器
当流的输入不受信任并且过滤器限制类的种类或实施约束时,可以在单个ObjectInputStream上设置过滤器。例如,可以要求流仅包含数字、字符串和其他应用程序指定的类型。
使用setObjectInputFilter方法设置自定义过滤器。设置自定义过滤器必须在从流中读取对象之前。
在下例中,setObjectInputFilter方法调用了dateTimeFilter方法(In the following example, the setObjectInputFilter method is invoked with the method.)(译注:从代码上来看,是dateTimeFiltersetObjectInputFilter方法将dateTimeFilter方法设置成了过滤器)。此过滤器仅接受java.time包中的类。dateTimeFilter方法的定义在把方法设置为自定义过滤器的示例代码中。
1 LocalDateTime readDateTime(InputStream is) throws IOException {
2 try (ObjectInputStream ois = new ObjectInputStream(is)) {
3 ois.setObjectInputFilter(FilterClass::dateTimeFilter);
4 return (LocalDateTime) ois.readObject();
5 } catch (ClassNotFoundException ex) {
6 IOException ioe = new StreamCorruptedException("class missing");
7 ioe.initCause(ex);
8 throw ioe;
9 }
10 }
设置进程级的自定义过滤器
你可以设置应用于全部ObjectInputStream的进程级过滤器,只要特定流没有覆盖它,它就会被调用。要实现过滤器,需要确定应用程序所需的类型和条件。一般来说,进程级过滤器用于拒绝特定的类或包,或限制数组大小、图深度或图的总大小。
进程级过滤器只需使用 ObjectInputFilter.Config 类的方法设置一次。过滤器可以是类、lambda 表达式、方法引用或模式的实例。
ObjectInputFilter filter = ...
ObjectInputFilter.Config.setSerialFilter(filter);
下例中,将 lambda 表达式设置为进程级过滤器。
ObjectInputFilter.Config.setSerialFilter(info -> info.depth() > 10 ? Status.REJECTED : Status.UNDECIDED);
下例中,将方法引用设置为进程级过滤器:
ObjectInputFilter.Config.setSerialFilter(FilterClass::dateTimeFilter);
将模式设置为自定义过滤器
可以使用ObjectInputFilter.Config.createFilter方法创建基于模式的自定义过滤器(情况简单时这很方便)。可以以系统属性或安全属性的形式创建基于模式的过滤器。用方法或lambda表达式实现基于模式的过滤器则更灵活。
过滤器模式可以接受或拒绝特定的类、包、模块,并且可以限制数组大小、图深度、总引用数量和流大小。模式不会匹配类的超类型或接口。
在下例中,过滤器允许example.File类并且拒绝example.Directory类。
ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("example.File;!example.Directory");
下例仅允许 example.File类。其他类都被拒绝。
ObjectInputFilter filesOnlyFilter = ObjectInputFilter.Config.createFilter("example.File;!*");
将类设置为自定义过滤器
可以用实现了java.io.ObjectInputFilter接口的类、lambda表达式或方法实现自定义过滤器。
过滤器通常是无状态的,对输入参数的检查互相之间没有关联。但是有状态的过滤器是可实现的。例如,可以实现这样的过滤器,在调用checkInput方法之间维护状态,以对流中的项目进行计数。
在下例中,FilterNumber类仅允许Number类的实例对象,并拒绝其他对象。
1 class FilterNumber implements ObjectInputFilter {
2 public Status checkInput(FilterInfo filterInfo) {
3 Class<?> clazz = filterInfo.serialClass();
4 if (clazz != null) {
5 return (Number.class.isAssignableFrom(clazz)) ? Status.ALLOWED : Status.REJECTED;
6 }
7 return Status.UNDECIDED;
8 }
9 }
在上例中:
checkInput方法的参数是一个ObjectInputFilter.FilterInfo对象。可以用该对象的方法访问要检查的类、数组大小、当前深度、对现有对象的引用数以及到目前为止读取的流大小。serialClass方法的返回值不是null,则表明正在创建新对象。然后检查该对象的类是不是Number。若是,则接受,否则拒绝。UNDECIDED。如果还有过滤器,则继续反序列化,直到该对象被接收或拒绝。如果没有其他过滤器,则接受该对象。将方法设置为自定义过滤器
可以用方法实现自定义过滤器。使用方法引用而不是内联的lambda表达式。
为单个流设置自定义过滤器中的示例代码使用了下例中定义的方法。dateTimeFilter
1 public class FilterClass {
2 static ObjectInputFilter.Status dateTimeFilter(ObjectInputFilter.FilterInfo info) {
3 Class<?> serialClass = info.serialClass();
4 if (serialClass != null) {
5 return serialClass.getPackageName().equals("java.time")
6 ? ObjectInputFilter.Status.ALLOWED
7 : ObjectInputFilter.Status.REJECTED;
8 }
9 return ObjectInputFilter.Status.UNDECIDED;
10 }
11 }
示例:只允许于java.base 模块中的类的过滤器
此自定义过滤器(作为方法实现)仅允许在JDK的基本模块中找到的类通过。此示例适用于JDK 9及更高版本。
1 static ObjectInputFilter.Status baseFilter(ObjectInputFilter.FilterInfo info) {
2 Class<?> serialClass = info.serialClass();
3 if (serialClass != null) {
4 return serialClass.getModule().getName().equals("java.base")
5 ? ObjectInputFilter.Status.ALLOWED
6 : ObjectInputFilter.Status.REJECTED;
7 }
8 return ObjectInputFilter.Status.UNDECIDED;
9 }
内置过滤器
JDK中有适用于Java远程方法调用(RMI)注册中心、RMI分布式垃圾回收器和Java管理扩展(JMX)的过滤器。应该为RMI注册中心和RMI分布式垃圾回收器指定自己的过滤器,以增强安全性。
RMI 注册中心的过滤器
注意:只把这些内置过滤器当作起点。可以编辑
sun.rmi.registry.registryFilterjdk.serialFilter系统属性来配置黑名单和/或扩展白名单,这能增强RMI 注册中心的安全性。若要保护整个应用程序,请将模式添加到jdk.serialFilter全局系统属性,以增强对没有自己的自定义过滤器的其他序列化用户的保护。
RMI 注册中心具有内置的白名单过滤器,该过滤器仅允许在注册中心中绑定的对象通过。允许的对象包括java.rmi.Remotejava.lang.Number、java.lang.reflect.Proxy、java.rmi.server.UnicastRef、java.rmi.activation.ActivationId、java.rmi.server.UID、java.rmi.server.RMIClientSocketFactory和java.rmi.server.RMIServerSocketFactory类的实例。
内置过滤器包括大小限制:
maxarray=1000000,maxdepth=20
请在sun.rmi.registry.registryFilter系统属性用模式定义一个过滤器,取代内置过滤器。如果定义的过滤器要么接受传递给过滤器的类,要么拒绝类或尺寸,那么内置过滤器不会调用。如果用户定义的过滤器既不接受也不拒绝任何内容,则内置过滤器会被调用。
用于 RMI 分布式垃圾回收器的过滤器
注意:仅将这些内置过滤器当作起点。编辑
sun.rmi.transport.dgcFilter系统属性可以配置黑名单和/或扩展白名单,这能为分布式垃圾回收器添加保护。若要保护整个应用程序,请将模式添加到jdk.serialFilter全局系统属性,以增强对没有自己的自定义过滤器的其他序列化用户的保护。
RMI 分布式垃圾回收器有一个内置的白名单过滤器,该过滤器接受有限的一组类。这组类包括java.rmi.server.ObjID、java.rmi.server.UID、java.rmi.dgc.VMID和java.rmi.dgc.Lease类的实例。
内置过滤器包括大小限制:
maxarray=1000000,maxdepth=20
请在sun.rmi.transport.dgcFilter系统属性用模式定义过滤器,用以取代内置过滤器。如果过滤器接受传递给过滤器的类,或者拒绝类或大小,则不会调用内置过滤器。如果取代过滤器不接受或拒绝任何内容,则调用内置过滤器。
用于 JMX 的过滤器
注意:仅将这些内置过滤器当作起点。编辑
jmx.remote.rmi.server.serial.filter.pattern管理属性可以配置黑名单和/或扩展白名单,这能增加对JMX的额外保护。若要保护整个应用程序,请将模式添加到jdk.serialFilter全局系统属性,以增强对没有自己的自定义过滤器的其他序列化用户的保护。
JMX 有一个内置的过滤器,该过滤器仅允许一组有限的类作为反序列化参数通过 RMI发送到服务器。该过滤器默认禁用。若要启用该过滤器,请在jmx.remote.rmi.server.serial.filter.pattern管理属性用模式定义过滤器。
该模式必须包括允许通过 RMI 作为参数发送到服务器的类型,以及它们所依赖的所有类型,外加javax.management.ObjectName和java.rmi.MarshalledObject类型。例如,若要将允许的类集合限制为Open MBean类型及其所依赖的类型,请将以下行添加到management.properties文件中。
com.sun.management.jmxremote.serial.filter.pattern=java.lang.*;java.math.BigInteger;java.math.BigDecimal;java.util.*;javax.management.openmbean.*;javax.management.ObjectName;java.rmi.MarshalledObject;!*
记录过滤器行为
可以打开日志记录以记录对序列化过滤器的调用的初始化、拒绝和接受。使用日志输出作为诊断工具,以查看正在反序列化的内容,并在配置白名单和黑名单时确认的设置。(Use the log output as a diagnostic tool to see what's being deserialized, and to confirm your settings when you configure whitelists and blacklists.)
启用日志记录后,过滤器操作将记录到 java.io.serialization序列化记录器中。
若要启用序列化过滤器的日志,请编辑$JDK_HOME/conf/logging.properties文件。
要记录被拒绝的调用,请添加
java.io.serialization.level = FINER
若要记录所有过滤器结果,请添加
java.io.serialization.level = FINEST 我正在使用i18n从头开始构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在rubyonrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
我有一个名为Post的类,我需要能够适应以下场景:如果用户选择了一个类别,则只显示该类别的帖子如果用户选择了一种类型,则只显示该类型的帖子如果用户选择了一个类别和类型,则只显示该类别中该类型的帖子如果用户没有选择任何内容,则显示所有帖子我想知道我的Controller是否不可避免地会因大量条件语句而显得粗糙...这是我解决此问题的错误方法-有谁知道我如何才能做到这一点?classPostsController 最佳答案 您最好遵循“胖模型,瘦Controller”的惯例,这意味着您应该将这种逻辑放在模型本身中。Post类应该能够报告
我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?
假设我必须(小型到中型)阵列:tokens=["aaa","ccc","xxx","bbb","ccc","yyy","zzz"]template=["aaa","bbb","ccc"]如何确定tokens是否以相同的顺序包含template的所有条目?(请注意,在上面的示例中,应忽略第一个“ccc”,从而由于最后一个“ccc”而导致匹配。) 最佳答案 这适用于您的示例数据。tokens=["aaa","ccc","xxx","bbb","ccc","yyy","zzz"]template=["aaa","bbb","ccc"]po
我仍然收到标题中的“错误”消息,但不知道如何解决。在ApplicationController中,classApplicationController在routes.rb#match'set_activity_account/:id/:value'=>'users#account_activity',:as=>:set_activity_account--thisdoesn'tworkaswell..resources:usersdomemberdoget:action_a,:action_bendcollectiondoget'account_activity'endend和User
对于用户模型,我有一个过滤器来检查用户的预订状态,该状态由整数值(0、1或2)表示。UserActiveAdmin索引页上的过滤器是通过以下代码实现的:filter:booking_status,as::select然而,这会导致下拉选项为0、1或2。当管理员用户从下拉列表中选择它们时,我更愿意自己将它们命名为“未完成”、“待定”和“已确认”之类的名称。有没有办法在不改变booking_status在模型中的表示方式的情况下做到这一点? 最佳答案 假设booking_status是模型中的枚举字段,您可以使用:过滤器:booking
首先,我使用的是rails3.1.3和来自master的carrierwavegithub仓库的分支。我使用after_init钩子(Hook)来确定基于属性的字段页面模型实例并为这些字段定义属性访问器将值存储在序列化哈希中(希望它清楚我是什么谈论)。这是我正在做的事情的精简版:classPage省略mount_uploader命令让我可以访问我想要的属性。但是当我安装uploader时出现错误消息说“nil类的未定义新方法”我在源代码中读到有方法read_uploader和扩展模块中的write_uploader。我如何必须覆盖这些来制作mount_uploader命令使用我的“虚拟
很高兴看到google代码:google-api-ruby-client项目,因为这对我来说意味着Ruby人员可以使用GoogleAPI-s来完善代码。虽然我现在很困惑,因为给出的唯一示例使用Buzz,并且根据我的实验,Google翻译(v2)api的行为必须与google-api-ruby-client中的Buzz完全不同。.我对“Explorer”演示示例很感兴趣——但据我所知,它并不是一个探索器。它所做的只是调用一个Buzz服务,然后浏览它已经知道的关于Buzz服务的事情。对我来说,Explorer应该让您“发现”所公开的服务和方法/功能,而不一定已经知道它们。我很想听听使用这个