关于漏洞
FastJson 的这个新漏洞在 1.2.68 及之前版本的 autotype 关闭的情况下仍然可以绕过限制反序列化,相比 1.2.47 版本的漏洞来讲这个版本的漏洞还是有一些限制的(关于 1.2.47 漏洞可以参考我的另一篇文章《Java 反序列化漏洞始末(3)— fastjson》[1]),例如 1.2.47 是可以绕过黑名单的限制的,而这个漏洞则无法绕过黑名单,并且需要类实现 AutoCloseable 接口。目前主要的 JNDI gadget 已经进了黑名单,还不允许反序列化类实现了 ClassLoader、DataSource、RowSet 接口,这就导致了绝大部分的 JNDI gadget 无法利用,所以本篇文章主要分享一下 gadget 的挖掘思路和漏洞的原理分析。
漏洞分析
这个漏洞的的成因和我的另一篇文章《fastjson 1.2.68 最新版本有限制 autotype bypass》[2]一致,都是由于期望类(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》[2] 这篇文章提到过,当 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 或更高版本•未能更新到 1.2.69 版本的请将 AutoCloseable、OutputStram、InputStream、RowSet 列入反序列化黑名单
References
[1]
《Java 反序列化漏洞始末(3)— fastjson》: https://b1ue.cn/archives/184.html[2]
《fastjson 1.2.68 最新版本有限制 autotype bypass》: https://b1ue.cn/archives/348.html
文由微信公众号安全档案