使用JEP290防御Java反序列化

简介

JEP290是Java官方提供的防御反序列化漏洞的措施,原本是Java9的新特性,后来被向下引入,在以下版本的JDK可用:

  • JDk8u121
  • JDK7u13
  • JDK6u141

JEP290提供了反序列化的过滤器,允许对传入的序列化数据流进行过滤

作用

  • 限制可用反序列化的类
  • 限制图的大小和复杂性
  • 允许RMI导出的对象验证调用中预期的类

创建基于模式的过滤器

基于模式的过滤器可以在命令行中或者java.security配置文件中定义

基于模式的过滤器语法

pattern的语法规则如下:

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

pattern例子:

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

创建过滤器

创建过滤器有几种方式:

  • 在启动参数中加-Djdk.serialFilter=pattern1.*;pattern2.*创建全局过滤器

  • java.security配置文件中加入jdk.serialFilter=pattern1.*;pattern2.*也能创建全局过滤器(会覆盖命令行参数)

  • 在代码中调用ObjectInputFilter.Config.createFilter创建过滤器(JDK8),然后用ObjectInputFilter.Config.setObjectInputFilter设置过滤器(会覆盖全局过滤器)

    1
    2
    
    ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("!*");
    ObjectInputFilter.Config.setObjectInputFilter(ois,filter);
    
  • 在JDK9以上是调用ObjectInputFilter.setObjectInputFilter方法设置过滤器

创建资源限制过滤器

  • 最大的数组长度: maxarray=100000;
  • 递归的最大深度:maxdepth=20;
  • 最多的引用数量: maxrefs=500;
  • 反序列化流的最大字节数:maxbytes=500000;

创建资源限制过滤器的命令如下:

1
-Djdk.serialFilter=maxarray=1000;maxbytes=1037;maxrefs=500;!com.package.A

创建自定义过滤器

自定义Filter需要实现ObjectInputFilter接口,然后在checkInput方法中自定义拦截规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class MyFilter implements ObjectInputFilter {
    private Class<?>[] whitelist = new Class[]{String.class, Integer.class, org.example.Allow.class};

    @Override
    public Status checkInput(FilterInfo filterInfo) {
        for (Class<?> i : whitelist) {
            if (filterInfo.serialClass() == i || filterInfo.serialClass() == null) {
                return Status.ALLOWED;
            }
        }
        return Status.REJECTED;
    }
}

JEP415

JEP290有两个问题:

  • 代码发布后无法更新过滤器
  • 无法对第三方库中的反序列化进行过滤

JEP415是Java17新特性,新增了serialFilterFactory,可以用jdk.serialFilterFactory指定这个属性。

可以指定上下文的反序列化过滤器,使用官方给的例子,首先定义一个FilterInThread

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class FilterInThread implements BinaryOperator<ObjectInputFilter> {
    private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();
    public FilterInThread() {}
    public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
        if (curr == null) {
            var filter = filterThreadLocal.get();
            if (filter != null) {
                filter = ObjectInputFilter.rejectUndecidedClass(filter);
            }
            if (next != null) {
                filter = ObjectInputFilter.merge(next, filter);
                filter = ObjectInputFilter.rejectUndecidedClass(filter);
            }
            return filter;
        } else {
            if (next != null) {
                next = ObjectInputFilter.merge(next, curr);
                next = ObjectInputFilter.rejectUndecidedClass(next);
                return next;
            }
            return curr;
        }
    }
    public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
        var prevFilter = filterThreadLocal.get();
        try {
            filterThreadLocal.set(filter);
            runnable.run();
        } finally {
            filterThreadLocal.set(prevFilter);
        }
    }
}

首先有个apply方法可以合并两个反序列化过滤器,返回新的过滤器,解决了第一个问题。

然后如果现在我不能直接操作ObjectInputStream,例如使用SignedObject执行反序列化,那就可以指定上下文的反序列化过滤器进行过滤:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var filterInThread = new FilterInThread();
ObjectInputFilter.Config.setSerialFilterFactory(filterInThread);
var filter = ObjectInputFilter.Config.createFilter("Dog");
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
filterInThread.doWithSerialFilter(filter, () -> {
    try{
        SignedObject so = new SignedObject(CC6.getObj(), privateKey, signingEngine);
        so.getObject();
    }catch (Exception e){
        e.printStackTrace();
    }
});

这里定义的过滤器只允许Dog类被反序列化,而如果换成其他的类就被拦截。

Refere

https://openjdk.org/jeps/290

https://openjdk.org/jeps/415

https://www.cnpanda.net/sec/968.html

https://docs.oracle.com/javase/10/core/serialization-filtering1.htm

updatedupdated2023-05-202023-05-20