FastJson是如何導致App Crash的

去年FastJson的嚴重漏洞

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

       具體漏洞如下,相信大家肯定也看到過類似的帖子,或者被公司的同事提醒過:


關於這次漏洞,開發者需要做什麼

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

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

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

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的時候大概都是這樣子的:

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

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

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()方法吧,方法具體實現也很簡單,我這裏也貼出來,大致如下:

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的懷抱呢?

關注我獲取更多知識或者投稿


作者:蘋果味的少年
鏈接:https://juejin.im/post/5ee1f026f265da770e1bdb33

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章