fastjson 1.x 升级到 2.x 后 PropertyNamingStrategy.SnakeCase 踩坑记录

935 阅读1分钟

项目中使用了 fastjson 1.2.78,但这个版本存在安全漏洞,需要升级到 fastjson 2.0.32。

漏洞说明如下:

  1. fastison <=1.2.68 反序列化远程代码执行漏洞:fastjson 采用黑白名单的方法来防御反序列化漏洞,导致当黑客不断发掘新的反序列化Gadgets类时,在autoType关闭的情况下仍然可能可以绕过黑白名单防御机制,造成远程命令执行漏洞。影响版本 fastjson <=1.2.68 fastison sec版本 <=sec9 安全版本 fastison >=1.2.69 fastison sec版本>=sec10
  2. Fastison版本低于1.2.58远程代码执行漏洞:当应用或系统使用 Fastjson 对由用户可控的 JSON字符串数据进行解析时,将可能导致远程代码执行的危害。影响版本:1.2.58以下版本
  3. fastjson <=1.2.80 反序列化任意代码执行漏洞:fastison 己使用黑白名单用于防御反序列化漏洞,经研究该利用在特定条件下可绕过默认autoType关闭限制,攻击远程服务器,风险影响较大。影响版本:fastjsons1.2.80

但 fastjson 1.2.78 和 fastjson 2.0.32 在 PropertyNamingStrategy 使用上略有不同:

所以,诉求就是既要升级到 2.0.32 版本,也要保持 1.2.78 版本中 PropertyNamingStrategy 不适用于 Map 中的 Key 这一特性;解决方案如下:

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.NameFilter;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson2.util.BeanUtils;
import lombok.Data;
import org.junit.Test;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * FastjsonTest
 *
 * @author Zexin Li
 * @date 2023-08-22 16:58
 */
public class FastjsonTest {

    // ==========>>>>>>>>>> 1.2.78 版本写法 <<<<<<<<<<==========

    private static final SerializeConfig serializeConfig = new SerializeConfig();

    static {
        serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
    }

    public void toJsonString_v1_2_78(Object obj) {
        return JSONObject.toJSONString(obj, serializeConfig);
    }

    // ==========>>>>>>>>>> 2.0.32 版本写法 <<<<<<<<<<==========

    private static class CamelCaseToUnderscoreFilter implements NameFilter {
        static final CamelCaseToUnderscoreFilter INSTANCE = new CamelCaseToUnderscoreFilter();

        @Override
        public String process(Object object, String name, Object value) {
            // 如果name在Map中,就原样显示
            if (object instanceof Map) {
                return name;
            }
            // 使用 PropertyNamingStrategy.SnakeCase 代码逻辑
            return BeanUtils.fieldName(name, PropertyNamingStrategy.SnakeCase.name());
        }
    }

    public void toJsonString_v2_0_32(Object obj) {
        return JSONObject.toJSONString(testObj, CamelCaseToUnderscoreFilter.INSTANCE);
    }
}

正文结束。


👇下面是实验代码。

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.PropertyNamingStrategy;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.NameFilter;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson2.util.BeanUtils;
import lombok.Data;
import org.junit.Test;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * FastjsonTest
 *
 * @author Zexin Li
 * @date 2023-08-22 16:58
 */
public class FastjsonTest {

    private static final SerializeConfig serializeConfig = new SerializeConfig();

    private static final ParserConfig parserConfig = new ParserConfig();

    static {
        serializeConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
        /* ==========>>>>>>>>>> 区别1 <<<<<<<<<<==========
        1.2.78 可以正常使用
        2.0.32 ParserConfig 没有 propertyNamingStrategy 属性了
         */
        // parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase;
    }

    @Data
    private static class TestObj {
        private String KEY;
        private String keyKey;
        private Map<Object, Object> mapKey = new LinkedHashMap<>();
    }

    @Test
    public void testMapToJsonString() {
        Map<Object, Object> originMap = new LinkedHashMap<>();
        originMap.put("KEY", "VALUE");
        originMap.put("keyKey", "valueValue");

        /* ==========>>>>>>>>>> 区别2 <<<<<<<<<<==========
        toJsonString 结果对比
        1.2.78 结果:{"KEY":"VALUE","keyKey":"valueValue"}
        2.0.32 结果:{"k_e_y":"VALUE","key_key":"valueValue"}
         */
        String jsonString = JSONObject.toJSONString(originMap, serializeConfig);
        System.out.println("toJSONString result: " + jsonString);
    }

    @Test
    public void testJsonStringToMap() {
        /*
        parseObject 结果对比
        1.2.78 结果:{KEY=VALUE, keyKey=valueValue}
        2.0.32 结果:{KEY=VALUE, keyKey=valueValue}
         */
        String jsonString = "{\"KEY\":\"VALUE\",\"keyKey\":\"valueValue\"}";
        LinkedHashMap<?, ?> newMap = JSONObject.parseObject(jsonString, LinkedHashMap.class, parserConfig);
        System.out.println("parseObject result: " + newMap);

        /*
        parseObject 结果对比
        1.2.78 结果:{k_e_y=VALUE, key_key=valueValue}
        2.0.32 结果:{k_e_y=VALUE, key_key=valueValue}
         */
        String jsonString2 = "{\"k_e_y\":\"VALUE\",\"key_key\":\"valueValue\"}";
        LinkedHashMap<?, ?> newMap2 = JSONObject.parseObject(jsonString2, LinkedHashMap.class, parserConfig);
        System.out.println("parseObject result: " + newMap2);
    }

    @Test
    public void testObjToJsonString() {
        TestObj originObj = new TestObj();
        originObj.setKEY("VALUE");
        originObj.setKeyKey("valueValue");

        /*
        toJsonString 结果对比
        1.2.78 结果:{"k_e_y":"VALUE","key_key":"valueValue"}
        2.0.32 结果:{"k_e_y":"VALUE","key_key":"valueValue"}
         */
        String jsonString = JSONObject.toJSONString(originObj, serializeConfig);
        System.out.println("toJSONString result: " + jsonString);
    }

    @Test
    public void testJsonStringToObj() {
        /*
        parseObject 结果对比
        1.2.78 结果:TestObj(KEY=VALUE, keyKey=valueValue)
        2.0.32 结果:TestObj(KEY=VALUE, keyKey=valueValue)
         */
        String jsonString = "{\"KEY\":\"VALUE\",\"keyKey\":\"valueValue\"}";
        TestObj newObj = JSONObject.parseObject(jsonString, TestObj.class, parserConfig);
        System.out.println("parseObject result: " + newObj);

        /*
        parseObject 结果对比
        1.2.78 结果:TestObj(KEY=VALUE, keyKey=valueValue)
        2.0.32 结果:TestObj(KEY=VALUE, keyKey=valueValue)
         */
        String jsonString2 = "{\"k_e_y\":\"VALUE\",\"key_key\":\"valueValue\"}";
        TestObj newObj2 = JSONObject.parseObject(jsonString2, TestObj.class, parserConfig);
        System.out.println("parseObject result: " + newObj2);
    }

    // ======================= 优化方案 =======================

    private static class CamelCaseToUnderscoreFilter implements NameFilter {
        static final CamelCaseToUnderscoreFilter INSTANCE = new CamelCaseToUnderscoreFilter();

        @Override
        public String process(Object object, String name, Object value) {
            // 如果name在Map中,就原样显示
            if (object instanceof Map) {
                return name;
            }
            // 使用 PropertyNamingStrategy.SnakeCase 代码逻辑
            return BeanUtils.fieldName(name, PropertyNamingStrategy.SnakeCase.name());
        }
    }

    @Test
    public void testMapToJsonString_FixBug() {
        Map<Object, Object> originMap = new LinkedHashMap<>();
        originMap.put("KEY", "VALUE");
        originMap.put("keyKey", "valueValue");
        originMap.put("KeyKey", "ValueValue");

        TestObj testObj = new TestObj();
        testObj.setMapKey(originMap);
        testObj.setKEY("VALUE");
        testObj.setKeyKey("valueValue");

        /*
        1.2.78 结果:{"k_e_y":"VALUE","key_key":"valueValue","map_key":{"KEY":"VALUE","keyKey":"valueValue","KeyKey":"ValueValue"}}
         */
        String jsonString_1_2_78 = JSONObject.toJSONString(testObj, serializeConfig);
        System.out.println("fastjson 1.2.78 toJSONString result: " + jsonString_1_2_78);

         /*
        2.0.32 结果:{"k_e_y":"VALUE","key_key":"valueValue","map_key":{"KEY":"VALUE","keyKey":"valueValue","KeyKey":"ValueValue"}}
         */
        String jsonString_2_0_32 = JSONObject.toJSONString(testObj, CamelCaseToUnderscoreFilter.INSTANCE);
        System.out.println("fastjson 2.0.32 toJSONString result: " + jsonString_2_0_32);
    }

}