简介
JEP290是Java官方提供的防御反序列化漏洞的措施,原本是Java9的新特性,后来被向下引入,在以下版本的JDK可用:
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