[TOC]
FastJson的AutoType
1 AutoType解析
fastJson的主要功能就是将Java Bean序列化成Json字符串,这样得到字符串之后就可以通过数据库等方式进行持久化。
但是,fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。
其实,对于Json框架来说,想要把一个Java对象转换成字符串,可以有两种选择:
- 基于属性
- 基于getter/setter
而我们所常用的JSON序列化框架中,FastJson和JackJson在把对象序列化成JSON字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,它是通过反射遍历该类中的所有属性,并把其值序列化成json。
1.1 type字段
假设我们有以下Java类:
1 | public interface Fruit { |
1 |
|
1 |
|
那么问题来了,我们上面定义的Fruit只是一个接口,序列化的时候fastJson能够把属性值正确序列化出来吗?如果可以的话,那么反序列化的时候FastJson会把这个fruit反序列化成什么类型呢?
我们尝试着验证一下,基于(fastjson 1.2.75):
1 | Store store = new Store(); |
我们创建了一个store,为他指定了名称,并且创建了一个Fruit的子类型Apple,然后将这个store使用 JSON.toJSONString 进行序列化,可以得到以下JSON内容:
1 | toJSONString : {"fruit":{"price":0.5},"name":"Hollis"} |
那么,这个fruit的类型到底是什么呢,能否反序列化成Apple呢?我们再来执行以下代码:
1 | Store newStore = JSON.parseObject(jsonString, Store.class); |
执行结果报错,我们尝试将Fruit转换成Apple,但是抛出了异常。
以上现象,我们知道,当一个类中包含了一个接口(或抽象类)的时候,在使用fastJson进行序列化的时候,会将类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型。
那么有什么办法解决这个问题呢,FastJson引用了AutoType,即在序列化的时候,把原始类型记录下来。
使用方法是通过SerializerFeature.WriteClassName
进行标记,即将上述代码中的
1 | String jsonString = JSON.toJSONString(store); |
修改成:
1 | String jsonString = JSON.toJSONString(store, SerializerFeature.WriteClassName) |
输出结果如下:
1 | { |
这就是AutoType,以及FastJson中引入AutoType的原因。
1.2 setAutoTypeSupport
首先,可以通过:
1 | ParserConfig.getGlobalInstance().isAutoTypeSupport(); // 获取是否允许AutoType(默认是false) |
尝试关闭AutoType后:
1 | ParserConfig.getGlobalInstance().setAutoTypeSupport(false); // 设置全局支持或禁止AutoType |
发现以上的代码还是可以正常的根据@type字段来反序列化。
但是如果使用了泛型,如下:
再有以下Java类:
1 |
|
再对Store进行封装:
1 | ParserConfig.getGlobalInstance().setAutoTypeSupport(false); |
输出结果:
1 | toJSONString : {"@type":"com.example.redis.entity.Result","data":{"@type":"com.example.redis.entity.Store","fruit":{"@type":"com.example.redis.entity.Apple","price":0.5},"name":"Hollis"}} |
若开启setAutoTypeSupport(true)
1 | toJSONString : {"@type":"com.example.redis.entity.Result","data":{"@type":"com.example.redis.entity.Store","fruit":{"@type":"com.example.redis.entity.Apple","price":0.5},"name":"Hollis"}} |
2 反序列化攻击
因为有了autoType功能,那么FastJson在对JSON字符串进行反序列化的时候,就会读取@type
到内容,试图把JSON内容反序列化成这个对象,并且会调用这个类的setter方法。
那么就可以利用这个特性,自己构造一个JSON字符串,并且使用@type
指定一个自己想要使用的攻击类库。
举个例子,黑客比较常用的攻击类库是com.sun.rowset.JdbcRowSetImpl
,这是sun官方提供的一个类库,这个类的dataSourceName支持传入一个rmi的源,当解析这个url的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。
而FastJson在反序列化的时候会调用目标类的setter方法,那么如果黑客在JdbcRowSetImpl的dataSourceName中设置一个想要执行的命令,那么就会导致很严重的后果
如通过以下方式定义一个JSON串,即可实现远程命令(在早期版本中,新版本中JdbcRowSetImpl已经被加了黑名单)
1 | {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true} |
这就是所谓的远程命令执行漏洞,即利用漏洞入侵到目标服务器,通过服务器执行命令。
3 AutoType安全模式
这些漏洞的利用几乎都是围绕AutoType来的,于是,在v1.2.68版本中,引入了safeMode,配置safeModel后,无论白名单和黑名单,都不支持autoType,可一定程度上缓解反序列化类变种攻击。
设置了safeModel后,@type字段不在生效,即当解析形如{“@type”: “com.java.class”}的JSON串时,将不再反序列化出对应的类。(无论白名单和黑名单,都不支持autoType)。
1 | ParserConfig.getGlobalInstance().setSafeMode(true); |
所以一般使用AutoType时建议使用指定白名单的方式。
1 | //全局开启AutoType,不建议使用 |