天天看點

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

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

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

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

fastjson大家一定都不陌生,這是阿裡巴巴的開源一個JSON解析庫,通常被用于将Java Bean和JSON 字元串之間進行轉換。

前段時間,fastjson被爆出過多次存在漏洞,很多文章報道了這件事兒,并且給出了更新建議。

但是作為一個開發者,我更關注的是他為什麼會頻繁被爆漏洞?于是我帶着疑惑,去看了下fastjson的releaseNote以及部分源代碼。

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

從2019年7月份釋出的v1.2.59一直到2020年6月份釋出的 v1.2.71 ,每個版本的更新中都有關于AutoType的更新。

下面是fastjson的官方releaseNotes 中,幾次關于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黑名單

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

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

那麼,什麼是AutoType?為什麼fastjson要引入AutoType?為什麼AutoType會導緻安全漏洞呢?本文就來深入分析一下。

fastjson的主要功能就是将Java Bean序列化成JSON字元串,這樣得到字元串之後就可以通過資料庫等方式進行持久化了。

但是,fastjson在序列化以及反序列化的過程中并沒有使用Java自帶的序列化機制,而是自定義了一套機制。

其實,對于JSON架構來說,想要把一個Java對象轉換成字元串,可以有兩種選擇:

1、基于屬性

2、基于setter/getter

而我們所常用的JSON序列化架構中,FastJson和jackson在把對象序列化成json字元串的時候,是通過周遊出該類中的所有getter方法進行的。Gson并不是這麼做的,他是通過反射周遊該類中的所有屬性,并把其值序列化成json。

假設我們有以下一個Java類:

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

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

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

以上代碼比較簡單,我們建立了一個store,為他指定了名稱,并且建立了一個Fruit的子類型Apple,然後将這個store使用<code>JSON.toJSONString</code>進行序列化,可以得到以下JSON内容:

那麼,這個fruit的類型到底是什麼呢,能否反序列化成Apple呢?我們再來執行以下代碼:

執行結果如下:

可以看到,在将store反序列化之後,我們嘗試将Fruit轉換成Apple,但是抛出了異常,嘗試直接轉換成Fruit則不會報錯,如:

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

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

使用方法是通過<code>SerializerFeature.WriteClassName</code>進行标記,即将上述代碼中的

修改成:

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

可以看到,使用<code>SerializerFeature.WriteClassName</code>進行标記後,JSON字元串中多出了一個<code>@type</code>字段,标注了類對應的原始類型,友善在反序列化的時候定位到具體類型

如上,将序列化後的字元串在反序列化,既可以順利的拿到一個Apple類型,整體輸出内容:

這就是AutoType,以及fastjson中引入AutoType的原因。

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

因為有了autoType功能,那麼fastjson在對JSON字元串進行反序列化的時候,就會讀取<code>@type</code>到内容,試圖把JSON内容反序列化成這個對象,并且會調用這個類的setter方法。

那麼就可以利用這個特性,自己構造一個JSON字元串,并且使用<code>@type</code>指定一個自己想要使用的攻擊類庫。

舉個例子,黑客比較常用的攻擊類庫是<code>com.sun.rowset.JdbcRowSetImpl</code>,這是sun官方提供的一個類庫,這個類的dataSourceName支援傳入一個rmi的源,當解析這個uri的時候,就會支援rmi遠端調用,去指定的rmi位址中去調用方法。

而fastjson在反序列化時會調用目标類的setter方法,那麼如果黑客在JdbcRowSetImpl的dataSourceName中設定了一個想要執行的指令,那麼就會導緻很嚴重的後果。

如通過以下方式定一個JSON串,即可實作遠端指令執行(在早期版本中,新版本中JdbcRowSetImpl已經被加了黑名單)

這就是所謂的遠端指令執行漏洞,即利用漏洞入侵到目标伺服器,通過伺服器執行指令。

在早期的fastjson版本中(v1.2.25 之前),因為AutoType是預設開啟的,并且也沒有什麼限制,可以說是裸着的。

從v1.2.25開始,fastjson預設關閉了autotype支援,并且加入了checkAutotype,加入了黑名單+白名單來防禦autotype開啟的情況。

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

因為fastjson預設關閉了autotype支援,并且做了黑白名單的校驗,是以攻擊方向就轉變成了"如何繞過checkAutotype"。

下面就來細數一下各個版本的fastjson中存在的漏洞以及攻擊原理,由于篇幅限制,這裡并不會講解的特别細節,如果大家感興趣我後面可以單獨寫一篇文章講講細節。下面的内容主要是提供一些思路,目的是說明寫代碼的時候注意安全性的重要性。

在fastjson v1.2.41 之前,在checkAutotype的代碼中,會先進行黑白名單的過濾,如果要反序列化的類不在黑白名單中,那麼才會對目标類進行反序列化。

但是在加載的過程中,fastjson有一段特殊的處理,那就是在具體加載類的時候會去掉className前後的<code>L</code>和<code>;</code>,形如<code>Lcom.lang.Thread;</code>。

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

而黑白名單又是通過startWith檢測的,那麼黑客隻要在自己想要使用的攻擊類庫前後加上<code>L</code>和<code>;</code>就可以繞過黑白名單的檢查了,也不耽誤被fastjson正常加載。

如<code>Lcom.sun.rowset.JdbcRowSetImpl;</code>,會先通過白名單校驗,然後fastjson在加載類的時候會去掉前後的<code>L</code>和<code>,變成了</code>com.sun.rowset.JdbcRowSetImpl`。

為了避免被攻擊,在之後的 v1.2.42版本中,在進行黑白名單檢測的時候,fastjson先判斷目标類的類名的前後是不是<code>L</code>和<code>;</code>,如果是的話,就截取掉前後的<code>L</code>和<code>;</code>再進行黑白名單的校驗。

看似解決了問題,但是黑客發現了這個規則之後,就在攻擊時在目标類前後雙寫<code>LL</code>和<code>;;</code>,這樣再被截取之後還是可以繞過檢測。如<code>LLcom.sun.rowset.JdbcRowSetImpl;;</code>

魔高一尺,道高一丈。在 v1.2.43中,fastjson這次在黑白名單判斷之前,增加了一個是否以<code>LL</code>未開頭的判斷,如果目标類以<code>LL</code>開頭,那麼就直接抛異常,于是就又短暫的修複了這個漏洞。

黑客在<code>L</code>和<code>;</code>這裡走不通了,于是想辦法從其他地方下手,因為fastjson在加載類的時候,不隻對<code>L</code>和<code>;</code>這樣的類進行特殊處理,還對<code>[</code>也被特殊處理了。

同樣的攻擊手段,在目标類前面添加<code>[</code>,v1.2.43以前的所有版本又淪陷了。

于是,在 v1.2.44版本中,fastjson的作者做了更加嚴格的要求,隻要目标類以<code>[</code>開頭或者以<code>;</code>結尾,都直接抛異常。也就解決了 v1.2.43及曆史版本中發現的bug。

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

但是好景不長,在更新到 v1.2.47 版本時,黑客再次找到了辦法來攻擊。而且這個攻擊隻有在autoType關閉的時候才生效。

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

因為在fastjson中有一個全局緩存,在類加載的時候,如果autotype沒開啟,會先嘗試從緩存中擷取類,如果緩存中有,則直接傳回。黑客正是利用這裡機制進行了攻擊。

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

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

<code>java.lang.Class</code>類對應的deserializer為MiscCodec,反序列化時會取json串中的val值并加載這個val對應的類。

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

如果fastjson cache為true,就會緩存這個val對應的class到全局緩存中

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

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

是以,黑客隻需要把攻擊類僞裝以下就行了,如下格式:

于是在 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 ,那麼也會通過校驗。

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

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

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

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

這個漏洞在 v1.2.69中被修複,主要修複方式是對于需要過濾掉的expectClass進行了修改,新增了4個新的類,并且将原來的Class類型的判斷修改為hash的判斷。

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

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

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

開啟safeMode方式如下:

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

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

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

目前fastjson已經釋出到了 v1.2.72版本,曆史版本中存在的已知問題在新版本中均已修複。

開發者可以将自己項目中使用的fastjson更新到最新版,并且如果代碼中不需要用到AutoType的話,可以考慮使用safeMode,但是要評估下對曆史代碼的影響。

因為fastjson自己定義了序列化工具類,并且使用asm技術避免反射、使用緩存、并且做了很多算法優化等方式,大大提升了序列化及反序列化的效率。

之前有網友對比過:

fastjson到底做錯了什麼?為什麼會被頻繁爆出漏洞?

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

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

知乎上有網友說:"溫少幾乎憑一己之力撐起了一個被廣泛使用JSON庫,而其他庫幾乎都是靠一整個團隊,就憑這一點,溫少作為“初心不改的阿裡初代開源人”,當之無愧。"

其實,關于fastjson漏洞的問題,阿裡内部也有很多人诟病過,但是诟病之後大家更多的是給予了解和包容。

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

"和發現漏洞相比,更糟糕的是有漏洞不知道被人利用。及時發現漏洞并更新版本修複是安全能力的一個展現。"

就在我寫這篇文章的時候,在釘釘上問了溫少一個問題,他竟然秒回,這令我很驚訝。因為那天是周末,周末釘釘可以做到秒回,這說明了什麼?

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

最後,知道了fastjson曆史上很多漏洞産生的原因之後,其實對我自己來說,我是"更加敢用"fastjson了...

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

參考資料:

https://github.com/alibaba/fastjson/releases

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

https://paper.seebug.org/1192/

https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ

http://www.lmxspace.com/2019/06/29/FastJson-反序列化學習

<b>👈🏻掃描二維碼關注他!</b>

【Hollis】公衆号,每天早上8:30為您準時推送一篇技術文章

本站采用開放的[知識共享署名-非商業性使用-相同方式共享]進行許可。

本站歡迎各種形式的轉載。請轉載時務必保留文章的原始出處及原文中外鍊,并不要擅自更改連結内容,否則保留追究法律責任的權利。