FastJson是如何致使App Crash的

去年FastJson的嚴重漏洞

       這要從去年6月份的一個高級漏洞提及,阿里雲監測到FastJson存在0day漏洞,攻擊者能夠利用該漏洞繞過黑名單策略進行遠程代碼執行。雖然具體來複現這個漏洞筆者沒有進行深刻研究,不過看上去就很嚴重的樣子,能夠遠程讓服務端執行指定的代碼,就意味着服務端都再也不安全了,更別提咱們處心積慮地各類加密的客戶端了,什麼加密混淆加固這一切努力都將白費。就像一個沒穿衣服的人,你戴再多帽子那也是徒勞。java

​       具體漏洞以下,相信你們確定也看到過相似的帖子,或者被公司的同事提醒過:json


關於此次漏洞,開發者須要作什麼

       這一次漏洞覆蓋面太廣了,可是阿里官方固然不會坐視無論,本身的各類項目裏都用着FastJson,任由病毒肆意蔓延,最後的損失將不可估量。因而在發現這個漏洞的第一時間FastJson官方就發出瞭解決方案,強烈建議你們升級FastJson到1.2.58版本以上。筆者也被公司的運維同窗通知到了該消息,因而第一時間對該問題進行了處理,當時還小嘚瑟了一下,感受沒有比單純升級一個sdk,改改版本號更簡單的需求了。我當時上Github上看了下FastJson的最新版本是1.2.60,因而就修改了項目中build.gradle中的FastJson版本號爲1.2.60。安全

解決此次漏洞,僅僅是改版本號這麼簡單嗎?

       升級之後,準備上傳代碼的時候我多留了一個心眼。按理說不會有什麼問題,可是當心駛得萬年船,內心想着既然是官方緊急發佈的版本,是否是在修復了這個漏洞的同時,會同時帶來其餘的問題呢?我首先隨便點了幾個使用了FastJson進行解析的頁面,並無發現問題。可是問題常常就出如今特殊狀況下,特別是第三方SDK對於本身就像個黑盒的時候。因而我習慣性地進行了一些健壯性測試,這是其中的一個測試案例,具體測試代碼以下:bash

String jsonStr = "";
JSONObject.parseObject(jsonStr,JavaBean.class);複製代碼

​       果不其然,當須要解析的字符串是空字符串時,將會發生crash,具體的錯誤堆棧信息以下:網絡

Caused by: com.alibaba.fastjson.JSONException: syntax error,except start with { or [,but actually start with EOF at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:688) at com.alibaba.fastjson.JSON.parseObject(JSON.java:383) at com.alibaba.fastjson.JSON.parseObject(JSON.java:287) at com.alibaba.fastjson.JSON.parseObject(JSON.java:560)運維

​       從報錯信息來看,像是傳空字符串時,FastJson並無進行健壯性處理。我第一反應是看一下升級之前的版本是否是也有相同的問題,因而我回退代碼再跑了一遍,發現並無問題。這隻能說明被我不幸言中了,正是1.2.60這版本新出現的問題。由於筆者項目裏有不少地方都是經過傳參的方式傳入json字符串,而後用FastJson進行解析的,難保有些地方因爲網絡很差,沒有請求到Json數據。或者是頁面間傳參丟失等,出現解析空字符串的狀況,並且具體哪些地方可能出現也很差排查。幸虧在上線前發現了這一問題,既然以前版本存在重大漏洞,而後最新版本又不能用。因此項目裏最終是用Gson來代替了FastJson上線,避免了可能存在的crash問題。測試

從源碼角度分析Crash來源

       那1.2.60版本爲何會報這個crash呢,咱們從源碼的角度來看看吧。這部分代碼很簡單,首先從使用的地方看起吧,咱們使用FastJson對Json字符串解析成Bean的時候大概都是這樣子的:gradle

JSONObject.parseObject(jsonStr,JavaBean.class);複製代碼

​       繼續點下去的話大概會通過幾個重載方法,而後最後一個parseObject()方法大概是這樣的:ui

public static <T> T parseObject(...){    //省略無用代碼
    DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
    T value = (T) parser.parseObject(clazz, null);
    //省略無用代碼
}複製代碼

​       咱們能夠看到,FastJson是這樣作的,首先初始化了一個DefaultJSONParser類,而後經過這個類的parseObject()方法來實現真正的邏輯,最後該方法的返回值就是最終咱們須要的Bean對象。接下來再看看DefaultJSONParser類中的parseObject()方法吧,方法具體實現也很簡單,我這裏也貼出來,大體以下:this

public static<T>T parseObject(...){
    //省略無用代碼
    if (deserializer.getClass() == JavaBeanDeserializer.class) {
        if (lexer.token()!= JSONToken.LBRACE && lexer.token()!=JSONToken.LBRACKET) {
        throw new JSONException("syntax error,except start with { or [,but actually start with "+ lexer.tokenName());
    }
        return (T) ((JavaBeanDeserializer) deserializer).deserialze(this, type, fieldName, 0);
    } else {
        return (T) deserializer.deserialze(this, type, fieldName);
    }
    //省略無用代碼}複製代碼

​       看到這裏報錯的位置就已經出來了,很明顯若是lexer.token()不是JSONToken.LBRACE或者JSONToken.LBRACKET的時候,就會拋出前面遇到的異常,那麼兩個枚舉是什麼意思呢?

​       咱們點到枚舉類裏去看一下,註釋已經很清楚了,這兩個枚舉分別表明左大括號和左中括號。也就是說想要解析必須以左大括號和左中括號開始,不然就拋出咱們看到的那個異常。那麼答案就水落石出了,當json爲空字符串的時候,明顯不是以左大括號和左中括號開始的,因此就crash了。

​       以前版本是並無這個問題的,那麼在1.2.55版本這一塊的代碼是怎麼處理的呢?一樣點進去,咱們發如今1.2.55裏面並無主動拋出這個異常:


​       而是在接着往下面解析的時候,將結果置爲Null進行返回了。


​       因此使用1.2.55版本的FastJson的話,結果是返回空的對象。若是咱們有在代碼裏對解析之後的對象進行判空的話,至少不會發生crash了。

截止到今天,FastJson是否已修復該問題

​       那截止到目前2020/6/11,FastJson是否修復了該問題呢?答案是確定的,咱們也來看下在最新的1.2.70版本里面FastJson源碼是怎麼處理的吧,代碼很是簡單:


       咱們能夠看到,相比1.2.55,在1.2.70中是將json字符串的判空邏輯前置了,因此也不會有問題了。

總結

​       本文分享了一個項目中實際發生的FastJson踩坑實例,由一個例子也引伸出了FastJson的部分簡單源碼。固然你們並不須要擔憂,由於在1.2.70裏面已經修復了這個問題。代碼裏有準備使用1.2.60版本的FastJson的同窗,或者是如今正在使用1.2.60版本的同窗須要注意了,要儘可能升級到1.2.70,防患於未然。

​       經過此次事件,有一個值得深思的事件彷佛冒了出來,那就是咱們不能徹底相信SDK會作好健壯性處理。雖然阿里的開發工程師和測試工程師都很強大,可是也不可避免地有可能會出現一些披露。因此咱們在升級SDK的時候,必定不能大意了,不要以爲改一個版本號就沒事了。就算是改版本號也要跟項目組裏的測試同窗反映一下,作一下簡單的迴歸測試。其次,本身也能夠作一些極限條件下的模擬測試,來儘可能減小上線之後可能出現的問題。

       有句話說得很對,那就是上線前測出一百個問題,實際嚴重程度比不上線上出的一個問題。上線前出問題最多隻是延長髮版的時間,這是徹底能夠接受的,可是線上出問題了,做爲非Hybrid開發的功能來講,又要通過漫長的bug修復、代碼Review、迴歸測試、申請應用市場、用戶從新下載安裝才能算是成功修復了,並且出問題期間的損失又有多少?

​       在這個任何行業競品App滿天飛的時代,有多少用戶會由於這個crash問題從而卸載App,轉而投向競品App的懷抱呢?


相關文章
相關標籤/搜索