1. 程式人生 > >如何通過程式碼審計挖掘REDos漏洞

如何通過程式碼審計挖掘REDos漏洞

寫這篇文章的目的一是由於目前網上關於java程式碼審計的資料實在是太少了,本人作為一個java程式碼審計的新手,深知學習java程式碼審計的難受之處,所以將自己學習過程中挖掘的一些漏洞寫成部落格發出來希望可以給後人一些幫助,同時也可以記錄下自己的成長過程。目的二是由於這次挖掘的漏洞REDOS,個人覺得這個漏洞還是很有意思的,很早就知道這個漏洞了但是從來沒有挖到過,終於在一個月前挖到了,很開心,記錄一下。

先介紹下REDOS這個漏洞吧,可能很多人並不是很熟悉這個漏洞,一是由於這種漏洞造成的影響是拒絕服務,影響似乎不是很嚴重;二是這種漏洞一般需要通過白盒測試才能發現,通常的黑盒測試很難發現這種漏洞。

OWASP的對它的描述是:正則表示式拒絕服務(Regular expression Denial of Service-ReDoS)是一種拒絕服務攻擊(Denial of Service ),它利用了大多數正則表示式實現可能會導致極端情況的原因,這些極端情況導致它們工作得非常緩慢(與輸入大小呈指數關係),然後攻擊者可以使用正則表示式導致程式進入這些極端情況,然後掛起很長時間。

說白了就是,應用程式中經常會有使用正則表示式去匹配字串的情況,(1)如果開發人員對正則表示式的原理並不是很熟悉寫出了類似“^(a+)+$”這樣的正則,(2)由於業務需求,正則是動態構造的,且受使用者控制。這兩種情況,程式都可能受到

REDOS攻擊。大家可能第一眼看到“^(a+)+$”不會覺得這個正則有什麼問題,這裡也就是為什麼說要懂原理才行。因為這裡其實涉及到了編譯原理的知識,我儘量講清楚:

正則表示式引擎分成兩類,一類稱為DFA(確定性有限狀態自動機),另一類稱為NFA(非確定性有限狀態自動機)。兩類引擎要順利工作,都必須有一個正則式和一個文字串,一個捏在手裡,一個吃下去。DFA捏著文字串去比較正則式,看到一個子正則式,就把可能的匹配串全標註出來,然後再看正則式的下一個部分,根據新的匹配結果更新標註。而NFA是捏著正則式去比文字,吃掉一個字元,就把它跟正則式比較,然後接著往下幹。一旦不匹配,就把剛吃的這個字元吐出來,一個一個吐,直到回到上一次匹配的地方。也就是說對於同一個字串中的每一個字元,

DFA都只會去匹配一次,比較快,但特性較少,而NFA則會去匹配多次,速度相比會慢很多但是特性多啊,所以用NFA作為正則表示式引擎的會多些。現在我們再來看^(a+)+$,大家應該能看出些問題了,當我們用它去匹配“aaaax”這個字串時,他需要嘗試2^4=16次才會失敗,當我們這個字串再長一點時,需要嘗試的次數會成指數增長,aaaaaaaaaaX就要經歷2^10=1024次嘗試。

如下圖:

 

可以看到javaphppython都是用的NFAJS中好像也是),也就是說這些語言中用了正則的api都是可能存在問題的哦。比如java中比較常見的split,replaceAll等,這些方法中既可以接受字串也可以接受正則表示式的。

說了這麼多,準備進入正題吧,為什麼一個月前就發現漏洞了,現在才寫部落格呢,本來是想等作者修復漏洞後再寫部落格的,但是因為github上的作者到現在都沒有回覆我,似乎並不準備修復,而我也懶得等了,直接將漏洞披露出來吧,畢竟漏洞影響不大,並且漏洞和程式上下文的聯絡也不大,復現的時候只需要將缺陷程式碼單獨拿出來在本地復現即可。

 

這整個java檔案是將檔案轉化為pdf格式的一個工具類,在轉化檔案格式的同時還需要 將檔名的字尾也修改下。如上兩個方法就是用來修改檔名字尾的。getOutputFilePath(String inputFilePath)用於將檔名的字尾修改為.pdf,而檔名字尾是通過getPostfix(String inputFilePath)這一方法截斷檔名中最後一個.後面的內容來獲取的。簡單介紹下String.replaceAll(a,b)的意思就是將String中的字串a全部替換成字串b。比如說”aabbcc”.replaceAll(“a”,”b”),輸出結果為“bbbbcc,上面也說了a可以是正則表示式 當”aabbcc”.replaceAll(“(a)+”,”b”),輸出結果為“fbbcc”。再回過頭看程式碼,如果我們已知引數inputFilePath是可控的,怎樣設定這個值才能觸發REDOS攻擊呢?

首先引數inputFilePath的字尾必須控制成一個真正表示式的樣,我們可以先設定為.(a+)+,.(a+)+作為正則表示式去匹配inputFilePath時,inputFilePath的前半部分必須符合這個正則才不至於匹配沒開始就結束了,所以inputFilePath的前半部分也應該類似.aaaa這樣,再和字尾拼湊起來就是.aaa.(a+)+

Ok,本地搭建環境測試一下:

 

發現程式執行的很快,關鍵是成功替換了字串。回顧之前的說的,之所以能造成REDOS攻擊是因為一直匹配失敗,引擎才會反覆嘗試導致攻擊的。怎樣讓他匹配失敗呢又一直嘗試呢?只需要將字尾.(a+)+改為.(a+)+$。這個正則的意思就是字串必須以a結尾才行。修改之後重新執行:

 

發現雖然執行很快但是不會替換字串了,也就是說匹配失敗。

那我們再將inputFilePath改下,讓程式匹配的時間長一些,如下圖:

 

發現程式一直在執行,說明攻擊成功了。

總結一下,挖掘REDOS漏洞,一是需要對程式中用到了正則的api有些瞭解(replaceAll只是最為常見的,其實還有很多),後面有時間的話我也會對這些api做些整理;二是要對正則有一定的瞭解才方便構造poc