多次触发FastJson漏洞的AutoType机制,你了解吗?

一个反序列化问题

在一次日志巡检过程中,发现线上业务出现报错。线上业务场景是:调用三方restful接口,根据接口返回json字符串内容,进行反序列化处理,业务中使用的json处理工具是FastJson(v1.2.71)。

报错是使用fastJson进行反序列化时出现的,问题json字符串如下:

{
    "name":"",
    "error":{
        "@type":"xxx.xxx.ErrorType",
        "message":"xxxxx"
    }
}

从数据结构上看,这个json字符串并没有什么特殊性,就是一个普通的字符串而已。从内容上看,比较特殊的地方在于多了一个特殊的字段“@type”,经过测试,反序列化报错,的确是因为这个特殊的字段造成的(把@type字段去掉,反序列化可以正常进行)。

那么@type是什么鬼,为什么有了字段就会产生问题呢?

@type和AutoType

使用过json序列化工具,处理包含接口、抽象类等无法直接实例化的字段类型时,你是否想过这些序列化工具是如何进行反序列化的吗?如果没有看明白这个问题的话,可以看一下下面的代码实例。

static class VehicleStore {
        private String name;
        private Vehicle vehicle;

       // 省略 setter/getter
    }

    interface Vehicle {
    }


    static class Car implements Vehicle {
        private BigDecimal price;
		// 省略 setter/getter
       
    }

上面代码定义了一个比较简单的数据类型,VehicleStore类中包含了一个类型是接口Vehicle 的字段 vehicle,接口Vehicle有一个实现类Car。

使用FastJson对VehicleStore进行序列化。

@Test
    public void deserializer() {
        VehicleStore store = new VehicleStore();
        store.setName("vehicleStore");
        Car car = new Car();
        car.setPrice(new BigDecimal(5000000));
        store.setVehicle(car);
        String jsonString = JSON.toJSONString(store);
    }

序列化后的json字符串内容如下:

 {"name":"vehicleStore","vehicle":{"price":5000000}}

在这个反序列化后的字符串内容中,vehicle字段的内容是 {“price”:5000000} ,无论从内容上、还是结构上看,根本看不出 {“price”:5000000} 和类型Car,有什么关系,那么用这个json字符串,进行反序列化,如何反序列化出 VehicleStore,并且把字段vehicle指定为Car呢?

其实是不能的,如果直接用FastJson进行反序列化,反序列化出来的VehicleStore中vehicle字段是空(没有任何字段内容)。
多次触发FastJson漏洞的AutoType机制,你了解吗?_第1张图片

总结来说:当一个类中包含了一个接口(或抽象类),在使用fastjson进行序列化的时候,会将子类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型

为了解决这个问题,在fastjson中引入了AutoType机制,简单来说,既然无法正常反序列化是序列化时把原始类型擦除掉了导致的,那么在序列化时,把类型信息给添加上可以了。的确,FastJson也是这样做的,具体做法如下:

序列化:
在进行序列化时,使用

JSON.toJSONString(xxxObj, SerializerFeature.WriteClassName);

序列化出来的内容会带上"@type"字段,记录被序列化的类型,弥补类型擦除的问题。序列化后,内容如下:

{"@type":"com.test.FastJsonTest$VehicleStore","name":"vehicleStore","vehicle":{"@type":"com.test.FastJsonTest$Car","price":5000000}}

反序列化:
反序列化时使用@type字段完成对子类/实现类的实例化,然后,再将相应的字段内容通过setter方法注入到实例化后的对象中,完成反序列化。

完整实例代码:


@Test
    public void deserializer() {
        VehicleStore store = new VehicleStore();
        store.setName("vehicleStore");
        Car car = new Car();
        car.setPrice(new BigDecimal(5000000));
        store.setVehicle(car);

        String jsonString = JSON.toJSONString(store, SerializerFeature.WriteClassName); 
        VehicleStore newStore = JSON.parseObject(jsonString, VehicleStore.class);
        Car car = (Car) newStore.getVehicle();
    }


问题解决

到这里在文章开头,遇到的@type是怎么回事儿,也就清楚了。那么为什么带上@type后,反序列化会报错呢?这里我直接说一下答案:直接原因是@type指定的类型,在系统中不存在,在反序列化时,找不到该类型。

解决问题的方法也很简单,既然@type指定的类型不存在,那么可以在系统中把需要的类型定义出来,不过@type指定的类型,是三方接口系统中定义的,如果把该类型引入到调用方系统中,那么对于调用方的侵入性很大,而且定义这个类型,仅仅为了解决这个问题,有很大解释成本在里面。

第二种解决方案:在反序列化时,忽略@type字段,这需要在反序列化时,添加Feature.IgnoreAutoType,完整的配置方式如下:

VehicleStore newStore1 = JSON.parseObject(jsonStr, VehicleStore.class, Feature.IgnoreAutoType);

到这里,开头的问题已经得到了解决,但是了解了AutoType机制后,你是否想过,如果把@Type指定为一个比较“特殊”的类型,有可能会对系统的安全产生比较大的威胁。

比如,把@type指定一个jdk中自带的类型 com.sun.rowset.JdbcRowSetImpl ,同时把字段dataSourceName设置为一个rmi数据源,那么对rmi数据源进行解析时,就完成了对rmi的调用,实现了对系统的远程命令执行。

具体操作过程如下(此漏洞,已修复,无法复现了,哈哈哈)

public void setDataSourceName(String var1) throws SQLException {
        if (this.getDataSourceName() != null) {
            if (!this.getDataSourceName().equals(var1)) {
                super.setDataSourceName(var1);// 注入攻击rmi源
                this.conn = null;
                this.ps = null;
                this.rs = null;
            }
        } else {
            super.setDataSourceName(var1);
        }
    } 


private Connection connect() throws SQLException {
        if (this.conn != null) {
            return this.conn;
        } else if (this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());// 调用rmi方法
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

其实FastJson的很多安全漏洞都和AutoType有关,当黑客发现了一个漏洞后,作者会立即进行相应的修复,在安全的攻防过程中,fastJson对AutoType进行了多个版本的更新:

1.2.59发布,增强AutoType打开时的安全性 fastjson
1.2.60发布,增加了AutoType黑名单,修复拒绝服务安全问题 fastjson
1.2.61发布,增加AutoType安全黑名单 fastjson
1.2.62发布,增加AutoType黑名单、增强日期反序列化和JSONPath fastjson
1.2.66发布,Bug修复安全加固,并且做安全加固,补充了AutoType黑名单 fastjson
1.2.67发布,Bug修复安全加固,补充了AutoType黑名单 fastjson
1.2.68发布,支持GEOJSON,补充了AutoType黑名单。(引入一个safeMode的配置,配置safeMode后,无论白名单和黑名单,都不支持autoType。) fastjson
1.2.69发布,修复新发现高危AutoType开关绕过安全漏洞,补充了AutoType黑名单 fastjson
1.2.70发布,提升兼容性,补充了AutoType黑名单

对此过程有感兴趣的老铁可以阅读一下下面这个文章:https://juejin.cn/post/6846687594130964488

你可能感兴趣的:(日常随笔,开发语言,FastJson,漏洞,AutoType,java)