浅谈JEP290

JEP290介绍

JDK Enhancement Proposal 简称JEP,是 JDK 增强提议的一个项目,目前索引编号已经达到了JEP415

JEP290的描述是Filter Incoming Serialization Data,即过滤传入的序列化数据。JEP290 是 Java 为了防御反序列化攻击而设置的一种过滤器,其在 JEP 项目中编号为290,因而通常被简称为JEP290。原本是Java9的新特性,后来被向下引入,在以下版本的JDK可用:

  • JDk8u121
  • JDK7u13
  • JDK6u141

作用

  • 提供一个限制反序列化类的机制,白名单或者黑名单
  • 限制反序列化的深度和复杂度
  • 为RMI远程调用对象提供了一个验证类的机制
  • 定义一个可配置的过滤机制,比如可以通过配置 properties文件的形式来定义过滤器

限制的情况

  • 反序列化类数组时的数组元素数 ( arrayLength )
  • 每个嵌套对象的深度( depth )
  • 当前数量对象引用 ( references ) 的数量
  • 当前消耗的字节数 ( streamBytes )

支持 3 种配置过滤器的方式

  • 自定义过滤器
  • 进程范围过滤器(也称为全局过滤器)
  • 用于 RMI 注册表和分布式垃圾收集 (DGC)使用的内置过滤器

规则

JEP 290 是根据特定的规则进进行过滤器的配置的,感觉像正则表达式,规则如下:

  • 模式用分号分隔
  • *匹配所有的类
  • !拒绝一个类
  • package.*匹配包中所有的类
  • package.**匹配包中所有的类及其子类

example:

  • com.package.AllowClass;!*:允许AllowClass拒绝其他类
  • com.package.A;com.package.B;!*:允许A类和B类,拒绝其他类
  • !com.package.DenyClass:拒绝DenyClass,允许其他类
  • !com.package.*拒绝com.package包中的类,允许其他类

JEP 290 核心类

JEP 290 涉及的核心类有: ObjectInputStream 类,ObjectInputFilter 接口,Config 静态类以及 Global 静态类。其中 Config 类是 ObjectInputFilter接口的内部类,Global 类又是Config类的内部类。

ObjectInputStream 类

JEP 290 进行过滤的具体实现方法是在 ObjectInputStream 类中增加了一个serialFilter属性和一个 filterChcek 函数,两者搭配来实现过滤的。

构造函数

有两个构造函数,我们需要关注的是在这两个构造函数中都会赋值 serialFilter 字段为 ObjectInputFilter.Config.getSerialFilter():

ObjectInputFilter.Config.getSerialFilter() 返回 ObjectInputFilter#Config 静态类中的 serialFilter静态字段

serialFilter 属性

serialFilter 属性是一个 ObjectInputFilter 接口类型,这个接口声明了一个 checkInput 方法(关于 ObjectInputFilter 后面会更细致的讲解)。

filterCheck 函数

filterCheck 函数逻辑可以分三步。

第一步,先会判断 serialFilter 属性值是否为空,只有不为空,才会进行后续的过滤操作。

第二步,将我们需要检查的 class ,以及 arryLength等信息封装成一个FilterValues对象。

传入到 serialFilter.checkInput 方法中,返回值为 ObjectInputFilter.Status 类型。

最后一步,判断 status 的值,如果 status 是 null 或者是 REJECTED 就会抛出异常。

ObjectInputStream 总结

到这里可以知道,serialFilter 属性就可以认为是 JEP 290 中的”过滤器”。过滤的具体逻辑写到 serialFilter 的checkInput 方法中,配置过滤器其实就是设置 ObjectInputStream 对象的 serialFilter属性。并且在 ObjectInputStream 构造函数中会赋值 serialFilter 为 ObjectInputFilter#Config 静态类的 serialFilter 静态字段。

ObjectInputFilter 接口

在低于 JDK 9 的时候的全限定名是 sun.misc.ObjectInputFIlter,JDK 9 及以上是 java.io.ObjectInputFilter 。

另外低于 JDK 9 的时候,获取和设置过滤器的方法是 getInternalObjectInputFiltersetInternalObjectInputFilter,JDK 9 以及以上则是 getObjectInputFiltersetObjectInputFIlter

先来看一下 ObjectInputFilter接口的结构:

有一个 checkInput 函数,一个静态类 Config ,一个 FilterInfo 接口,一个 Status 枚举类。

函数式接口

@FunctionalInterface 注解表明, ObjectInputFilter 是一个函数式接口。在这里我们其实只需要关心函数式接口怎么赋值,函数式接口的赋值可以是: lambda 表达式或者是方法引用,当然也可以赋值一个实现了这个接口的对象。

lambda 赋值:

ObjectInputFilter configuredFilter = (ObjectInputFilter.FilterInfo var1) -> {
    System.out.println("filed");
    return null;
};

使用函数引用赋值,比如 RMI 中 RegistryImpl 使用的就是函数引用赋值:

Config 静态类

Config 静态类是 ObjcectInputFilter 接口的一个内部静态类。

  • Config#configuredFilter 静态字段

configuredFilter 是一个静态字段,所以调用 Config 类的时候就会触发 configuredFilter 字段的赋值。

可以看到会拿到 jdk.serailFilter 属性值,如果不为空,会返回 createFilter(props)的结果(createFilter 实际返回的是一个 Global 对象)。

jdk.serailFilter 属性值获取的方法用两种,第一种是获取 JVM 的 jdk.serialFilter 属性,第二种通过在 %JAVA_HOME%\conf\security\java.security 文件中指定 jdk.serialFilter 来设置。另外从代码中可以看到,优先选择第一种。

  • Config#createFilter 方法

Config#createFilter 则会进一步调用 Global.createFilter方法,这个方法在介绍 Global 类的时候会说,其实就是将传入的 JEP 290 规则字符串解析到Global对象的 filters 字段上,并且返回这个 Global 对象。

  • serialFilter 的赋值

在获取完configuredFilter 的值后会赋值 Config.configuredFilter 到 Config.serialFilter 上。

  • Config#getSerialFilter 和setSerialFilter方法

getSerialFilter返回 Config.serialFilter字段值;setSerialFilter作用为当Config.serialFilter属性为空的时候可以为其赋值。

  • Config 静态类总结

Config 静态类在初始化的时候,会将Config.serialFilter 赋值为一个Global对象,这个Global 对象的filters字段值是jdk.serailFilter属性对应的 Function 列表。(关于 Global 对象介绍下面会说到,大家先有这么一个概念)

而 ObjectInputStream 的构造函数中,正好取的就是 Config.serialFilter 这个静态字段 , 所以设置了 Config.serialFilter 这个静态字段,就相当于设置了 ObjectInputStream 类全局过滤器

比如可以通过配置 JVM 的 jdk.serialFilter 或者 %JAVA_HOME%\conf\security\java.security 文件的 jdk.serialFilter 字段值,来设置 Config.serialFilter ,也就是设置了全局过滤。

另外还有就是一些框架,在开始的时候设置也会设置 Config.serialFilter ,来设置 ObjectInputStream 类的全局过滤。 weblogic 就是,在启动的时候会设置 Config.serialFilter 为 WebLogicObjectInputFilterWrapper 对象。

Global 静态类

Global 静态类是 Config 类中的一个内部静态类。

Global 类的一个重要特征是实现了 ObjectInputFilter 接口,实现了其中的 checkInput 方法。所以 Global 类可以直接赋值到 ObjectInputStream.serialFilter 上。

Global#filters 字段

是一个函数列表。

Global#checkInput 方法

Global 类的 checkInput 会遍历 filters 去检测要反序列化的类。

Global 中的构造函数

Global 中的构造函数会解析 JEP 290 规则。Global 中的构造函数的作用用一句话总结就是:解析 JEP 290 规则为对应的 lambda 表达式,然后添加到 Global.filters 。

Global#createFilter 方法

传入规则字符串,来实例化一个 Global 对象。

Global 类的总结

Global 实现了ObjectInputFilter接口,所以是可以直接赋值到 ObjectInputStream.serialFilter 上。

Global#filters 字段是一个函数列表。

Global 类中的 chekInput 方法会遍历 Global#filters 的函数,传入需要检查的 FilterValues进行检查(FilterValues 中包含了要检查的 class, arrayLength,以及 depth 等)。

过滤器

配置过滤器其实就是设置 ObjectInputStream 类中的 serialFilter 属性。

过滤器的类型有两种,第一种是通过配置文件或者 JVM 属性来配置的全局过滤器,第二种则是来通过改变 ObjectInputStream 的 serialFilter 属性来配置的局部过滤器。

全局过滤器

设置全局过滤器,其实就是设置Config静态类的 serialFilter 静态字段值。

具体原因上面说过,是因为在 ObjectInputStream 的两个构造函数中,都会为 serialFilter 属性赋值为 ObjectInputFilter.Config.getSerialFilter() 。而 ObjectInputFilter.Config.getSerialFilter 就是直接返回 Config#serialFilter。

jdk.serailFilter

在介绍 Config 静态类的时候说到,Config 静态类初始化的时候,会解析 jdk.serailFilter 属性设置的 JEP 290 规则到一个 Global 对象的 filters 属性,并且会将这个 Global 对象赋值到 Config 静态类的 serialFilter 属性上。

所以,这里 Config.serialFilter 值默认是解析 jdk.serailFilter 属性得到得到的 Global 对象。

weblogic 全局过滤器

在 weblogic 启动的时候,会赋值 Config.serialFilter 为 WebLogicObjectInputFilterWrapper 。

用一段话来阐述 weblogic 中 全局过滤器赋值的流程就是:

weblogic 启动的时候,会调用 WeblogicObjectInputFilter 的 initializeInternal 方法进行初始化,首先会new JreFilterApiProxy 对象,这个对象相当于JEP 290 有关操作的代理对象,里面封装了操作 Config 静态类的方法。然后会 new 一个 WeblogicFilterConfig 对象,这个对象在 new 的时候会把 weblogic 的黑名单赋值到 WeblogicFilterConfig 对象的属性中。之后,会从WeblogicFilterConfig 对象属性中取 serialFilter ,调用 JreFilterApiProxy 对象的 setGlobalFilter 来赋值 Config.serailFilter 。

局部过滤器

设置局部过滤器的意思是在 new objectInputStream 对象之后,再通过改变单个 ObjectInputStream 对象的 serialFilter字段值来实现局部过滤。

改变单个 ObjectInputStream 对象的 serialFilter 字段是有两种方法:

1.通过调用 ObjectInputStream 对象的 setInternalObjectInputFilter 方法,进行当前的ObjectInputStream 进行过滤:

注:低于 JDK 9 的时候,是 getInternalObjectInputFiltersetInternalObjectInputFilter,JDK 9 以及以上是 getObjectInputFiltersetObjectInputFIlter

2.通过调用 ObjectInputFilter.Config.setObjectInputFilter ,传入需要进行过滤ObjectInputStream 对象和ObjectInputFilter对象,实际上也是调用上面的setInternalObjectInputFilter 方法:

局部过滤器典型的例子是 RMI Registry 、RMI 分布式垃圾收集器(DCG)和 Java 管理扩展(JMX)

RMI Registry

RMI Registry 有一个内置的白名单过滤器,允许将对象绑定到注册表中。它包括的情况如下:

  • java.rmi.Remote
  • java.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

RMI DCG

RMI 分布式垃圾收集器有一个内置的白名单过滤器,它接受一组有限的类。它包括的情况如下:

  • java.rmi.server.ObjID
  • java.rmi.server.UID
  • java.rmi.dgc.VMID
  • java.rmi.dgc.Lease

内置过滤器包括大小限制:

maxarray=1000000,maxdepth=20

除了这些类之外,用户还可以使用sun.rmi.registry.registryFilter(针对RMI Registry)和sun.rmi.transport.dgcFilter(针对DGC)系统或安全属性添加自己的自定义过滤器

JMX

对于JMX 过滤器,可以在进行RMIServer.newClient远程调用以及通过 RMI 向服务器发送反序列化参数时,指定要使用的反序列化过滤器模式字符串;还可以使用该management.properties文件向默认代理提供过滤器模式字符串

自定义过滤器

当反序列化要求与整个应用程序中的任何其他反序列化过程不同时,就会出现自定义过滤器的配置场景;可以通过实现ObjectInputFilter接口并重写checkInput(FilterInfo filterInfo)方法来创建自定义过滤器,感觉这也是局部过滤器的一种,需要自己创建后添加:

static class VehicleFilter implements ObjectInputFilter {
        final Class<?> clazz = Vehicle.class;
        final long arrayLength = -1L;
        final long totalObjectRefs = 1L;
        final long depth = 1l;
        final long streamBytes = 95L;

        public Status checkInput(FilterInfo filterInfo) {
            if (filterInfo.arrayLength() < this.arrayLength || filterInfo.arrayLength() > this.arrayLength
                    || filterInfo.references() < this.totalObjectRefs || filterInfo.references() > this.totalObjectRefs
                    || filterInfo.depth() < this.depth || filterInfo.depth() > this.depth || filterInfo.streamBytes() < this.streamBytes
                    || filterInfo.streamBytes() > this.streamBytes) {
                return Status.REJECTED;
            }

            if (filterInfo.serialClass() == null) {
                return Status.UNDECIDED;
            }

            if (filterInfo.serialClass() != null && filterInfo.serialClass() == this.clazz) {
                return Status.ALLOWED;
            } else {
                return Status.REJECTED;
            }
        }
    }

在JDK 9 中,oracle 向 ObjectInputStream 类里添加了两个方法(getObjectInputFiltersetObjectInputFilter),允许为当前的 ObjectInputStream 设置或者获取自定义的过滤器:

public class ObjectInputStream
    extends InputStream implements ObjectInput, ObjectStreamConstants {

    private ObjectInputFilter serialFilter;
    ...
    public final ObjectInputFilter getObjectInputFilter() {
        return serialFilter;
    }

    public final void setObjectInputFilter(ObjectInputFilter filter) {
        ...
        this.serialFilter = filter;
    }
    ...
} 

JDK 8 中跟局部过滤器一样,通过调用 ObjectInputFilter.Config.setObjectInputFilter ,传入需要进行过滤ObjectInputStream 对象和ObjectInputFilter对象,然后调用到ObjectInputStream .setInternalObjectInputFilter

JEP290 值得注意的点

  • JEP290需要手动设置,只有设置了之后才会有过滤,没有设置的话就还是可以正常的反序列化漏洞利用
  • JEP290默认只为 RMI 注册表(RMI Register层)、 RMI分布式垃圾收集器(DGC层)以及 JMX 提供了相应的内置过滤器

JEP290的绕过

对于JEP290的绕过其实要基于有没有配置全局过滤器,如果没有,那么有可能在应用程序级别中利用反序列化漏洞,但如果配置了全局过滤器,那么只能通过发现新的gadget链去利用。

配置了全局过滤器

未配置全局过滤器

  • CVE-2018-4939 (2018) –> Spring Framework RmiInvocationHandler,它可以将任意对象传递给RemoteInvocation类(利用的是任意对象作为参数)
  • unmarshalValue方法(2020年1月在JDK 8u242-b07、 11.0.6+10、13.0.2+5、14.0.1+2中修复,Java 版本 9、10 和 12 未修复)

总结

JEP 290 主要是在 ObjectInputStream 类中增加了一个serialFilter属性和一个 filterChcek 函数,其中 serialFilter就可以理解为过滤器。

在 ObjectInputStream 对象进行 readObject 的时候,内部会调用 filterChcek 方法进行检查,filterCheck方法中会对 serialFilter属性进行判断,如果不是 null ,就会调用 serialFilter.checkInput 方法进行过滤。

设置过滤器本质就是设置 ObjectInputStream 的 serialFilter 字段值,设置过滤器可以分为设置全局过滤器和设置局部过滤器:

  1. 设置全局过滤器是指,通过修改 Config.serialFilter这个静态字段的值来达到设置所有 ObjectInputStream对象的 serialFilter值 。具体原因是因为 ObjectInputStream 的构造函数会读取Config.serialFilter的值赋值到自己的serialFilter字段上,所有就会导致所有 new 出来的 ObjectInputStream对象的 serailFilter 都为Config.serialFilter的值。
  2. 设置局部过滤器是指,在 new ObjectInputStream 的之后,再修改单个 ObjectInputStream 对象的 serialFilter 字段值。

参考资料

漫谈 JEP 290

JEP290的基本概念

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇