原文連結:https://mp.weixin.qq.com/s/LdTKPgDyOULw2S_FVsM6sg
老專案中使用到多種json類庫,沒有統一管理。最近開啟全新的專案,準備對json類庫進行統一,這樣不僅能夠壓縮jar包的大小,也能夠避免某個類庫的漏洞導致系統問題。
其實,就在前幾個月因為FastJson的漏洞,已經全面升級過一次FastJson的版本。現在專案中有用FastJson,有用gson,也有用Jackson。雖然用的類庫比較多,但使用的場景並不多,還在可控範圍之內。
這篇文章重點講講對FastJson的一些調研,雖然最終決定強制在專案中禁用FastJson,但在放棄之前,還是要學習一下這個類庫的。
FastJson簡介Fastjson是阿里巴巴的開源JSON解析庫,基於Java語言,支援JSON格式的字串與JavaBean之間的相互轉換。它採用一種“假定有序快速匹配”的演算法,把JSON Parse的效能提升到了極致。
由於介面簡單易用,已經被廣泛使用在快取序列化,協議互動,Web輸出等各種應用場景中。
FastJson的簡單示例先用一個簡單的示例來演示一下FastJson的使用。先在專案中引入FastJson類庫:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version></dependency>
關於版本至少要在1.2.70以上,為啥?之前版本的漏洞太多。
定義個JavaBean,我們拿User為例:
public class User { private String userName; private int age; private String address; // getter/setter}
使用例項:
public static void main(String[] args) { String json = "{\"address\":\"Beijing\",\"age\":28,\"user_name\":\"Tom\"}"; // 將json轉換為JavaBean User user = JSONObject.parseObject(json, User.class); System.out.println(user); // 將JavaBean轉換為json String result = JSONObject.toJSONString(user); System.out.println(result);}
例項中先將json字串透過parseObject轉換成User物件,然後又將User物件透過toJSONString方法轉換成json。用起來是不是非常方便?
同時在構造json時你是否發現json字串中有“user_name”這樣的格式,FastJson預設會將這種下劃線格式的key,與JavaBean中駝峰格式的屬性進行繫結。
執行程式,列印結果:
User(userName=Tom, age=28, address=Beijing){"address":"Beijing","age":28,"userName":"Tom"}
可以看出成功執行。
FastJson還有其他一些常用的API,比如:
public static final Object parse(String text); // 把JSON文字parse為JSONObject或者JSONArray public static final JSONObject parseObject(String text); // 把JSON文字parse成JSONObject public static final <T> T parseObject(String text, Class<T> clazz); // 把JSON文字parse為JavaBean public static final JSONArray parseArray(String text); // 把JSON文字parse成JSONArray public static final <T> List<T> parseArray(String text, Class<T> clazz); //把JSON文字parse成JavaBean集合 public static final String toJSONString(Object object); // 將JavaBean序列化為JSON文字 public static final String toJSONString(Object object, boolean prettyFormat); // 將JavaBean序列化為帶格式的JSON文字 public static final Object toJSON(Object javaObject); //將JavaBean轉換為JSONObject或者JSONArray。
透過上述API還可以實現:json字串與JSONArray之間的轉換、json字串與javaBean之間的轉換、json字串-陣列型別與javaBean之間的轉換、JavaList與JsonArray之間的轉換等。
為什麼決定放棄FastJson透過上面的示例來看FastJson的API使用起來也是非常簡單,而且它的特點,也就是賣點就是“快”。
雖然網上有各種測試,質疑FastJson的“快”,但排除測試者測試用例或環境的影響,整體來看FastJson並不比市面上的其他同類框架慢。
那麼放棄使用的原因是什麼呢?
可以看出FastJson排行第四,僅次於第三位的JSON In Java。如果考慮到國內大多數用阿里映象,那麼FastJson的排位要更靠前一些,但與Jackson相比差距還是有的。
設計與程式碼質量在國外沒有更受推廣的原因大概有兩個:推廣(外加英文文件)和程式碼質量。
外國友人不喜歡FastJson是因為感覺程式碼質量不高。知乎上有一篇相關的文章,雖然寫2016年,但也可以參考一下(連結:https://www.zhihu.com/question/44199956)。
對於上述原因,我個人倒是更看重高贊回答中的總結“用很多投機取巧的的做法去實現所謂的'快',而失去了原本應該相容的java特性,對json標準遵循也不嚴格”。
開源Issues在寫這篇文章時,看了一下GitHub上專案的Issues,還有大量的需要修復的問題。而且版本還在頻繁的更新,修復升級。
還有1488個問題處於Open狀態!看到此處,真的有些擔心了。用的人多,提問題的人多,也可以從另外一個方面來說可能更安全,但如果還有這麼多問題待解決,還是有些恐怖的。
漏洞修復歷史同時,前段時間FastJson多次被爆存在漏洞,而這些漏洞都與FastJson中的一個AutoType特性有關。
從2019年7月份釋出的v1.2.59一直到2020年6月份釋出的 v1.2.71 ,每個版本的升級中都有關於AutoType的升級。
1.2.59釋出,增強AutoType開啟時的安全性 fastjson1.2.60釋出,增加了AutoType黑名單,修復拒絕服務安全問題 fastjson1.2.61釋出,增加AutoType安全黑名單 fastjson1.2.62釋出,增加AutoType黑名單、增強日期反序列化和JSONPath fastjson1.2.66釋出,Bug修復安全加固,並且做安全加固,補充了AutoType黑名單 fastjson1.2.67釋出,Bug修復安全加固,補充了AutoType黑名單 fastjson1.2.68釋出,支援GEOJSON,補充了AutoType黑名單。(引入一個safeMode的配置,配置safeMode後,無論白名單和黑名單,都不支援autoType。) fastjson1.2.69釋出,修復新發現高危AutoType開關繞過安全漏洞,補充了AutoType黑名單 fastjson1.2.70釋出,提升相容性,補充了AutoType黑名單
那麼什麼是AutoType?為什麼又會導致漏洞呢?
對於JSON框架Java物件轉換成字串通常可以基於屬性或setter/getter方法。FastJson和Jackson是透過遍歷出該類中的所有getter方法進行的,Gson是透過反射遍歷該類中的所有屬性,並把其值序列化成json。。
當一個類中包含了一個介面(或抽象類),在使用FastJson進行序列化的時候,會將子型別抹去,只保留介面(抽象類)的型別,使得反序列化時無法拿到原始型別。
因此,FastJson引入了AutoType,在序列化時把原始型別記錄下來。
有了autoType功能,FastJson在對JSON字串進行反序列化時,會讀取@type到內容,試圖把JSON內容反序列化成物件,並且會呼叫它的setter方法。利用這個特性,就可以構造一個JSON字串,並且使用@type指定一個自己想要使用的攻擊類庫。
小結雖然FastJson有這麼多問題,雖然決定不再使用FastJson,但同樣如知乎網友說的那樣“溫少幾乎憑一己之力撐起了一個被廣泛使用JSON庫,而其他庫幾乎都是靠一整個團隊,就憑這一點,溫少作為“初心不改的阿里初代開源人”,當之無愧。”對於FastJson的漏洞問題還是給予理解和包容。