loading...
FastJson的AutoType
Published in:2022-02-17 | category: 第三方插件
Words: 1.6k | Reading time: 6min | reading:

[TOC]

FastJson的AutoType

1 AutoType解析

参考https://juejin.cn/post/6846687594130964488

fastJson的主要功能就是将Java Bean序列化成Json字符串,这样得到字符串之后就可以通过数据库等方式进行持久化。

但是,fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。

其实,对于Json框架来说,想要把一个Java对象转换成字符串,可以有两种选择:

  1. 基于属性
  2. 基于getter/setter

而我们所常用的JSON序列化框架中,FastJson和JackJson在把对象序列化成JSON字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,它是通过反射遍历该类中的所有属性,并把其值序列化成json。

1.1 type字段

假设我们有以下Java类:

1
2
public interface Fruit {
}
1
2
3
4
5
6
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Apple implements Fruit{
private BigDecimal price;
}
1
2
3
4
5
6
7
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Store {
private String name;
private Fruit fruit;
}

那么问题来了,我们上面定义的Fruit只是一个接口,序列化的时候fastJson能够把属性值正确序列化出来吗?如果可以的话,那么反序列化的时候FastJson会把这个fruit反序列化成什么类型呢?


我们尝试着验证一下,基于(fastjson 1.2.75):

1
2
3
4
5
6
7
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内容:

1
2
toJSONString : {"fruit":{"price":0.5},"name":"Hollis"}

那么,这个fruit的类型到底是什么呢,能否反序列化成Apple呢?我们再来执行以下代码:

1
2
3
4
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进行标记,即将上述代码中的

1
String jsonString = JSON.toJSONString(store);

修改成:

1
String jsonString = JSON.toJSONString(store, SerializerFeature.WriteClassName)

输出结果如下:

1
2
3
4
5
6
7
8
9
{
"@type":"com.example.redis.entity.Store",
"fruit":{
"@type":"com.example.redis.entity.Apple", //多了@type映射全类名,反序列就可以找到对应的类
"price":0.5
},
"name":"Hollis"
}

这就是AutoType,以及FastJson中引入AutoType的原因。

1.2 setAutoTypeSupport

首先,可以通过:

1
2
ParserConfig.getGlobalInstance().isAutoTypeSupport(); // 获取是否允许AutoType(默认是false)
ParserConfig.getGlobalInstance().setAutoTypeSupport(true); // 设置全局支持禁止AutoType

尝试关闭AutoType后:

1
ParserConfig.getGlobalInstance().setAutoTypeSupport(false);	// 设置全局支持或禁止AutoType

发现以上的代码还是可以正常的根据@type字段来反序列化。

但是如果使用了泛型,如下:

再有以下Java类:

1
2
3
4
5
6
7
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Result<T>{
private T data;
}

再对Store进行封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

输出结果:

1
2
3
4
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)

1
2
3
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))) //可以正常反序列化

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
2
3
4
5
//全局开启AutoType,不建议使用
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
// 建议使用这种方式,小范围指定白名单
ParserConfig.getGlobalInstance().addAccept("xxx.xxx.")
// addAccept不支持通配符,可以直接指定包名,则该包下的所有类,都支持AutoType
Prev:
Mybatis一级缓存和二级缓存
Next:
浅析ResponseBodyAdvice接口的理解和实际应用
catalog
catalog