关于漏洞
fastjson 的这个新漏洞在 1.2.68 及之前版本的 autotype 关闭的情况下仍然可以绕过限制反序列化,相比 1.2.47 版本的漏洞来讲这个版本的漏洞还是有一些限制的(关于 1.2.47 漏洞可以参考我的另一篇文章《Java 反序列化漏洞始末(3)— fastjson》),例如 1.2.47 是可以绕过黑名单的限制的,而这个漏洞则无法绕过黑名单,并且需要类实现 AutoCloseable 接口。目前主要的 JNDI gadget 已经进了黑名单,还不允许反序列化类实现了 ClassLoader、DataSource、RowSet 接口,这就导致了绝大部分的 JNDI gadget 无法利用,所以本篇文章主要分享一下 gadget 的挖掘思路和漏洞的原理分析。
漏洞分析
这个漏洞的的成因和我的另一篇文章《fastjson 1.2.68 最新版本有限制 autotype bypass》一致,都是由于期望类(expectClass)导致的,这个漏洞的期望类范围更大,更容易找到具有危害的 gadget。
首先看 DefaultJSONParser#parseObject
这里将 @type 指定的类作为条件去获取 Deserialzer 对象。
在ParserConfig#getDeserializer
方法中不满足条件所以到了最后一步通过ParserConfig#createJavaBeanDeserializer
方法来构造 JavaBeanDeserializer
在这一步创建了 JavaBeanDeserializer 对象,而漏洞也就发生在 JavaBeanDeserializer 类中。
现在回到 DefaultJSONParser#parseObject
应该走下一步 JavaBeanDeserializer#deserialze
方法。
用期望类的思路,可以找到此处有两个方法使用了 ParserConfig#checkAutoType
且指定了期望类。
一个是 deserialzeArrayMapping()
另一个是 deserialze()
在 deserialze()
方法中又做了一次 checkAutoType 检测,此处直接将第二个 @type 的类名,和前面构造 JavaBeanDeserializer 对象时指定的期望类直接传了进来。
我在 《fastjson 1.2.68 最新版本有限制 autotype bypass》 这篇文章提到过,当 checkAutoType(String typeName, Class<?> expectClass, int features)
方法的 typeName 实现或继承自 expectClass,就会通过检验。
但还有三个问题,会阻碍 gadget 的触发。
boolean expectClassFlag;
if (expectClass == null) {
expectClassFlag = false;
} else if (expectClass != Object.class && expectClass != Serializable.class && expectClass != Cloneable.class && expectClass != Closeable.class && expectClass != EventListener.class && expectClass != Iterable.class && expectClass != Collection.class) {
expectClassFlag = true;
} else {
expectClassFlag = false;
}
第一个问题是期望类的黑名单,里面包括了大部分常用的父接口和父类,却唯独少了一个 java.lang.AutoCloseable
。
这也就是为什么 AutoCloseable 为什么可以通过校验的第一个原因,第二个原因是TypeUtils#mappings
里有 AutoCloseable 类。
第二个问题是黑名单类,fastjson 在 denyHashCodes 里几乎把常见的容易造成漏洞的类都加进了黑名单,这就造成了攻击成本变高,如果要利用漏洞,只能花费更多的时间去寻未被发现的常用库 gadget。
第三个问题是父类、父接口黑名单,fastjson 在判断期望类之前将继承自 ClassLoader、DataSource、RowSet 的类直接抛出异常。
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz) || RowSet.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}
而常用的 JNDI RCE 类基本上都继承自 DataSource 和 RowSet,所以能找到的 JNDI gadget 基本都无法在这个漏洞中使用。
以上三点足够让大部分常见的 gadget 无法使用,所以需要换一种 gadget 挖掘思路。
挖掘 gadget
关于 gadget 的挖掘思路我主要是寻找关于输入输出流的类来写文件,IntputStream 和 OutputStream 都是实现自 AutoCloseable 接口的,而且也没有被列入黑名单,所以只要找到合适的类,还是可以进行文件读写等高危操作的。
JNDI
前面说到,这个漏洞基本无法使用 JNDI,实际上并不完全是,当 fastjson 小于 1.2.51 时,还是可以通过实现了 RowSet 接口的类进行 JNDI 反序列化,但实际上已经没有什么危害,大多数都更新到了 1.2.60+ 版本。
文件读写
我寻找 gadget 时的条件是这样的。
- 需要一个通过 set 方法或构造方法指定文件路径的 OutputStream
- 需要一个通过 set 方法或构造方法传入字节数据的 OutputStream,并且可以通过 set 方法或构造方法传入一个 OutputStream,最后可以通过 write 方法将传入的字节码 write 到传入的 OutputStream
- 需要一个通过 set 方法或构造方法传入一个 OutputStream,并且可以通过调用 toString、hashCode、get、set、构造方法 调用传入的 OutputStream 的 flush 方法
以上三个组合在一起就能构造成一个写文件的利用链,最终我挑选出了三个符合条件的类作为演示。
漏洞复现
漏洞的原理摸清了,接下来我将用本地的模拟环境复现一遍漏洞。
写文件
复制文件
总结
本文旨在给安全研究者提供一种反序列化漏洞 gadget 挖掘思路,文中 POC 代码暂不公开。
修复方案
- 更新到 1.2.69 或更高版本