FastJson autoType is not support 基于FastJson的通用泛型解决方案 使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support 安全升级公告

最近项目里用到了阿里巴巴的fastjson工具,遇到一些问题,记录分享一下

github说明:

fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。

使用:

添加maven依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.54</version>
</dependency>

API使用

String text = JSON.toJSONString(obj); //序列化
VO vo = JSON.parseObject("{...}", VO.class); //反序列化

但有时候会出现

使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support

解决方法是

在RedisSerializer<T>的实现类中添加白名单

//添加白名单
static {
ParserConfig.getGlobalInstance().addAccept("com.glz.oauthmanager");
}
FastJson autoType is not support
基于FastJson的通用泛型解决方案
使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support
安全升级公告
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    //添加白名单
    static {
        ParserConfig.getGlobalInstance().addAccept("com.glz.oauthmanager");
    }

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return (T) JSON.parseObject(str, clazz);
    }

}
FastJson autoType is not support
基于FastJson的通用泛型解决方案
使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support
安全升级公告

如果还是不行,官方还有其他解决办法

逆风的方向,更适合飞翔!

https://www.cnblogs.com/nisiweiLIQIYONG/p/10843581.html

由于项目使用的是fastjson,也无法换成其他的序列化框架,所以研究了一下他对泛型序列化和反序列化的支持能力,最终解决了这个问题。

要达成的目标

我的封装方式属于通用封装,我要达到的目标是如下的使用方式:

放入数据:

 Map<String, OffheapDTO> mapxx = new HashMap<>();
        mapxx.put("1",new OffheapDTO().create());
        mapxx.put("2",new OffheapDTO().create());
        mapxx.put("3",new OffheapDTO().create());
        mapxx.put("4",new OffheapDTO().create());
        mapxx.put("5",new OffheapDTO().create());
        offheapCacheWrapper.putMap("maptest", mapxx);

获取数据:

 Map<String, OffheapDTO> resultmap = offheapCacheWrapper.queryMap("cachemap")

OffheapDTO对象的定义方式如下:

class OffheapDTO implements Serializable, Comparable{
    private String name;
    private String address;
    private String mark;
    private int order;
   //省略getset
}

也就是我可以随意的把任何对象进行序列化操作,然后可以随意的把任何已经序列化的对象,反序列化回来。

第一版代码代码,未中其意

 
putMap方法代码如下:
    public <T> void putMap(String key, T value, long expireSeconds) {
        try {
            EntityWrapper<T> entityWrapper = new EntityWrapper<>().create(key, value, expireSeconds);
            initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).put(key, JSON.toJSONString(entityWrapper));
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "putMapSingle with expires exception:", ex);
            throw ex;
        }
    }

queryMap方法代码如下:

    public <T> T queryMap(String key) {
        try {
            Object result = initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).get(key);
            if(result == null){
                return null;
            }
            //反序列化出entityWrapper
            EntityWrapper entityWrapper = JSON.parseObject(result.toString());
            return (T)entityWrapper.getEntity();
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "queryMap exception:", ex);
            return null;
        }
    }

结果当我反序列化的时候,调用resultmap.get("1")的时候,提示我无法将jsonObject转变成OffheapDTO. 调试进去发现,List对象里面装载的仍然是jsonObject数据。初次尝试失败。

第二版代码,苦尽甘来

之后翻看了百度,查阅了大量资料,然后看到了关于TypeReference的代码,之后将queryMap方法修改如下:

    public <T> T queryMap(String key) {
        try {
            Object result = initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).get(key);
            if(result == null){
                return null;
            }
            //反序列化出entityWrapper
            EntityWrapper entityWrapper = JSON.parseObject(result.toString(),new TypeReference<EntityWrapper<T>>() {});
            return (T)entityWrapper.getEntity();
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "queryMap exception:", ex);
            return null;
        }
    }

注意代码中黄色部分。

然后当我再次进行反序列化的时候,我发现resultMap.get(“1”)已经可以拿到正常的OffheapDTO对象了。心中一喜,然后运行resultMap.get(“1”).getName(), 居然又报错,提示无法将jsonObject转变成OffheapDTO对象,发现原来存储的字段,居然都是jsonObject类型。这次就有点慌了。

第三版代码,蓦然回首

不过想想fastjson这么成熟,定然有前人的*,所以就继续查阅资料,终于查到了setAutoTypeSupport这个属性,没想到一试,居然解决了问题。

首先,程序启动的时候,需要开启这个属性,至于这个属性真正的意义,去翻阅fastjson文档,我这里就不赘述了:

      //开启fastjson autotype功能(不开启,造成EntityWrapper<T>中的T无法正常解析)
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

然后,在序列化的时候,需要附带上序列化的class名称(黄色标记部分):

    public <T> void putMap(String key, T value, long expireSeconds) {
        try {
            EntityWrapper<T> entityWrapper = new EntityWrapper<>().create(key, value, expireSeconds);
            initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).put(key, JSON.toJSONString(entityWrapper, SerializerFeature.WriteClassName));
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "putMapSingle with expires exception:", ex);
            throw ex;
        }
    }

最后,在反序列化的时候,利用TypeReference进行类型指定即可:

    public <T> T queryMap(String key) {
        try {
            Object result = initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).get(key);
            if(result == null){
                return null;
            }
            //反序列化出entityWrapper
            EntityWrapper entityWrapper = JSON.parseObject(result.toString(),new TypeReference<EntityWrapper<T>>() {});
            return (T)entityWrapper.getEntity();
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "queryMap exception:", ex);
            return null;
        }
    }

这样,无论你的类有多复杂,都可以搞定,比如像下面这样的:

Map<String,List< OffheapDTO>> resultmap = offheapCacheWrapper.queryMap("maptest");

甚至这样的:

List<Map<String,List<Set<OffheapDTO>>>> resultmap = offheapCacheWrapper.queryMap("maptest");

https://www.cnblogs.com/scy251147/p/9451879.html

#打开AutoType功能 在1.2.25之后的版本,以及所有的.sec01后缀版本中,autotype功能是受限的,和之前的版本不同,如果在升级的过程中遇到问题,可以通过以下方法配置。

一、添加autotype白名单

添加白名单有三种方式,三选一,如下:

1. 在代码中配置

ParserConfig.getGlobalInstance().addAccept("com.taobao.pac.client.sdk.dataobject."); 

如果有多个包名前缀,分多次addAccept

2. 加上JVM启动参数

    -Dfastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao. 

如果有多个包名前缀,用逗号隔开

3. 通过fastjson.properties文件配置。

在1.2.25/1.2.26版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:

fastjson.parser.autoTypeAccept=com.taobao.pac.client.sdk.dataobject.,com.cainiao. // 如果有多个包名前缀,用逗号隔开

二、打开autotype功能

如果通过配置白名单解决不了问题,可以选择继续打开autotype功能,fastjson在新版本中内置了多重防护,但是还是可能会存在一定风险。两种方法打开autotype,二选一,如下:

1、JVM启动参数

-Dfastjson.parser.autoTypeSupport=true

2、代码中设置

ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 

如果有使用非全局ParserConfig则用另外调用setAutoTypeSupport(true);

三、配置autoType黑名单

打开AutoType之后,是基于内置黑名单来实现安全的,但黑名单是穷举不完的,如果发现了新的风险类,可以通过以下配置来增加黑名单:

1. 配置JVM启动参数

-Dfastjson.parser.deny=xx.xxx
  • 这里的xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开

2. 通过fastjson.properties来配置

在1.2.25之后的版本支持通过类路径的fastjson.properties文件来配置,配置方式如下:

-Dfastjson.parser.deny=xx.xxx 
  • 这里的xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开

3. 代码中配置

ParserConfig.getGlobalInstance().addDeny("xx.xxx");
  • 这里的xx.xxx是包名前缀,如果有多个包名前缀,用逗号隔开

https://github.com/alibaba/fastjson/wiki/enable_autotype

使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support

新建的GenericFastJson2JsonRedisSerializer里面添加白名

FastJson autoType is not support
基于FastJson的通用泛型解决方案
使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support
安全升级公告

添加:

static {
        ParserConfig.getGlobalInstance().addAccept("com.xxx.xxx.bo");
        ParserConfig.getGlobalInstance().addAccept("com.xxx.xxx.redis");
    }
参考:https://blog.csdn.net/InnovationAD/article/details/84341687

https://www.cnblogs.com/xyj179/p/10095250.html

FastJson autoType is not support
基于FastJson的通用泛型解决方案
使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support
安全升级公告

FastJson autoType is not support
基于FastJson的通用泛型解决方案
使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support
安全升级公告

总结:在使用fastjson类序列化时,尤其是redis这个序列的支持,在类中要加保证一默认的构造方法,否则在进行反序列化时,fastjosn根据typeName进行反序列化,会发生该异常

安全升级公告

最近发现fastjson在1.2.24以及之前版本存在远程代码执行高危安全漏洞,为了保证系统安全,请升级到1.2.28/1.2.29/1.2.30/1.2.31或者更新版本。

1.2.29//1.2.30/1.2.31是在1.2.28版本上修复了一些大家升级过程中遇到的问题的版本,非安全问题,如果升级到1.2.25~1.2.28以及各种sec01版本的,也是没有安全问题的。

1.2.25/1.2.26/1.2.27/1.2.28/1.2.29/1.2.30都是在升级的过程中修复不兼容问题发布的过度版本,如果你是在此之前升级到这些版本,不用因为这次的安全问题再次升级。

更新方法

1. Maven依赖配置更新

通过maven配置更新,使用最新版本,如下:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.31</version>
</dependency>

<!-- 1.2.41是最新的版本 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.41</version>
</dependency>

注意,版本号1.2.3~1.2.9版本都比1.2.31小,都是需要升级的。

2. 直接下载

常见问题

1. 升级遇到不兼容问题怎么办?

1.2.28/1.2.29/1.2.30/1.2.31已经修复了绝大多数兼容问题,但是总会有一些特殊的用法导致不兼容,如果你遇到不兼容问题,通过 https://github.com/alibaba/fastjson/wiki/incompatible_change_list 查看不兼容问题,链接的后面提供了遇到不兼容问题之后的使用相应的sec01版本解决办法。

2. 升级之后报错autotype is not support

安全升级包禁用了部分autotype的功能,也就是"@type"这种指定类型的功能会被限制在一定范围内使用。如果你使用场景中包括了这个功能,https://github.com/alibaba/fastjson/wiki/enable_autotype 这里有一个介绍如何添加白名单或者打开autotype功能。

3. 通过配置打开autotype之后是否存在安全漏洞

在1.2.28/1.2.29以及所有的.sec01版本中,有多重保护,但打开autotype之后仍会存在风险,不建议打开,而是使用一个较小范围的白名单。打开autoType建议升级到最新版本1.2.37以上

4. Android环境使用是否需要升级

目前未发现漏洞对Android系统产生影响,在Android环境中使用不用升级。

5. 升级遇到问题希望提供支持怎么办?

作者愿意帮助大家一起解决问题,如果遇到文档中没说明到的问题,请通过如下方式联系作者:

6. 有没有漏洞利用详情可以提供

为了保证更多用户的安全,目前不适合扩散漏洞利用的细节

7. 是否有在WAF上检测的办法

检测post内容中是否包含如下字符

"@type"

注意,为了减少误报,包括双引号

8. 检测当前使用版本的是否有问题

  1. 通过maven dependency检测
    如果是maven工程,在代码根目录下执行如下命令,如果有返回,就是需要升级
mvn dependency:tree | grep "com.alibaba.fastjson:"
 | grep -v sec01 | grep -v 1.2.25 | grep -v 1.2.26 | grep -v 1.2.27 | grep -v 1.2.28 | grep -v 1.2.29 | grep -v 1.2.30 | grep -v 1.2.31
  1. 在lib目录下执行如下脚本命令,可以判断版本是否有问题
ls | grep fastjson | grep jar
 | grep -v sec01 | grep -v 1.2.25 | grep -v 1.2.26 | grep -v 1.2.27
 | grep -v 1.2.28 | grep -v 1.2.29 | grep -v 1.2.30 | grep -v 1.2.31

请注意,为了方便阅读,加上了换行符,请使用时把上面的三行合并成一行。

  1. 看打开的文件中是否包含fastjson
sudo -u admin lsof -X | grep -v 1.2.25 | grep -v 1.2.26 | grep -v 1.2.27
 | grep -v 1.2.28 | grep -v 1.2.29 | grep -v 1.2.30 | grep -v 1.2.31

请注意,为了方便阅读,加上了换行符,请使用时把上面的两行合并成一行。另外通过lsof检测,在tomcat某些场景是检测不出来的,最好在lib目录下用ls检测(第2中方法)。

参考:



作者:xiaolyuh
链接:https://www.jianshu.com/p/a92ecc33fd0d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

由于项目使用的是fastjson,也无法换成其他的序列化框架,所以研究了一下他对泛型序列化和反序列化的支持能力,最终解决了这个问题。

要达成的目标

我的封装方式属于通用封装,我要达到的目标是如下的使用方式:

放入数据:

 Map<String, OffheapDTO> mapxx = new HashMap<>();
        mapxx.put("1",new OffheapDTO().create());
        mapxx.put("2",new OffheapDTO().create());
        mapxx.put("3",new OffheapDTO().create());
        mapxx.put("4",new OffheapDTO().create());
        mapxx.put("5",new OffheapDTO().create());
        offheapCacheWrapper.putMap("maptest", mapxx);

获取数据:

 Map<String, OffheapDTO> resultmap = offheapCacheWrapper.queryMap("cachemap")

OffheapDTO对象的定义方式如下:

class OffheapDTO implements Serializable, Comparable{
    private String name;
    private String address;
    private String mark;
    private int order;
   //省略getset
}

也就是我可以随意的把任何对象进行序列化操作,然后可以随意的把任何已经序列化的对象,反序列化回来。

第一版代码代码,未中其意

 
putMap方法代码如下:
    public <T> void putMap(String key, T value, long expireSeconds) {
        try {
            EntityWrapper<T> entityWrapper = new EntityWrapper<>().create(key, value, expireSeconds);
            initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).put(key, JSON.toJSONString(entityWrapper));
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "putMapSingle with expires exception:", ex);
            throw ex;
        }
    }

queryMap方法代码如下:

    public <T> T queryMap(String key) {
        try {
            Object result = initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).get(key);
            if(result == null){
                return null;
            }
            //反序列化出entityWrapper
            EntityWrapper entityWrapper = JSON.parseObject(result.toString());
            return (T)entityWrapper.getEntity();
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "queryMap exception:", ex);
            return null;
        }
    }

结果当我反序列化的时候,调用resultmap.get("1")的时候,提示我无法将jsonObject转变成OffheapDTO. 调试进去发现,List对象里面装载的仍然是jsonObject数据。初次尝试失败。

第二版代码,苦尽甘来

之后翻看了百度,查阅了大量资料,然后看到了关于TypeReference的代码,之后将queryMap方法修改如下:

    public <T> T queryMap(String key) {
        try {
            Object result = initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).get(key);
            if(result == null){
                return null;
            }
            //反序列化出entityWrapper
            EntityWrapper entityWrapper = JSON.parseObject(result.toString(),new TypeReference<EntityWrapper<T>>() {});
            return (T)entityWrapper.getEntity();
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "queryMap exception:", ex);
            return null;
        }
    }

注意代码中黄色部分。

然后当我再次进行反序列化的时候,我发现resultMap.get(“1”)已经可以拿到正常的OffheapDTO对象了。心中一喜,然后运行resultMap.get(“1”).getName(), 居然又报错,提示无法将jsonObject转变成OffheapDTO对象,发现原来存储的字段,居然都是jsonObject类型。这次就有点慌了。

第三版代码,蓦然回首

不过想想fastjson这么成熟,定然有前人的*,所以就继续查阅资料,终于查到了setAutoTypeSupport这个属性,没想到一试,居然解决了问题。

首先,程序启动的时候,需要开启这个属性,至于这个属性真正的意义,去翻阅fastjson文档,我这里就不赘述了:

      //开启fastjson autotype功能(不开启,造成EntityWrapper<T>中的T无法正常解析)
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);

然后,在序列化的时候,需要附带上序列化的class名称(黄色标记部分):

    public <T> void putMap(String key, T value, long expireSeconds) {
        try {
            EntityWrapper<T> entityWrapper = new EntityWrapper<>().create(key, value, expireSeconds);
            initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).put(key, JSON.toJSONString(entityWrapper, SerializerFeature.WriteClassName));
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "putMapSingle with expires exception:", ex);
            throw ex;
        }
    }

最后,在反序列化的时候,利用TypeReference进行类型指定即可:

    public <T> T queryMap(String key) {
        try {
            Object result = initMapContainer(OffheapCacheConst.MAP_CONTAINER_FOR_STRING).get(key);
            if(result == null){
                return null;
            }
            //反序列化出entityWrapper
            EntityWrapper entityWrapper = JSON.parseObject(result.toString(),new TypeReference<EntityWrapper<T>>() {});
            return (T)entityWrapper.getEntity();
        } catch (Exception ex) {
            logger.error(OffheapCacheConst.PACKAGE_CONTAINER + "queryMap exception:", ex);
            return null;
        }
    }

这样,无论你的类有多复杂,都可以搞定,比如像下面这样的:

Map<String,List< OffheapDTO>> resultmap = offheapCacheWrapper.queryMap("maptest");

甚至这样的:

List<Map<String,List<Set<OffheapDTO>>>> resultmap = offheapCacheWrapper.queryMap("maptest");

新建的GenericFastJson2JsonRedisSerializer里面添加白名

FastJson autoType is not support
基于FastJson的通用泛型解决方案
使用Redis 配置替换fastjson 反序列化报错 com.alibaba.fastjson.JSONException: autoType is not support
安全升级公告

添加:

static {
        ParserConfig.getGlobalInstance().addAccept("com.xxx.xxx.bo");
        ParserConfig.getGlobalInstance().addAccept("com.xxx.xxx.redis");
    }
参考:https://blog.csdn.net/InnovationAD/article/details/84341687