資料來源(作者:Eugene Rojavski):
https://www.checkmarx.com/blog/hacker-vs-struts2-game-has-no-ending
活躍於網路安全產業的人可能在2017年就聽過JavaFramework Struts 2的傳聞。總而言之,駭客能夠利用Struts 2的安全漏洞竊取數以億計的個資(PII)記錄。該漏洞(CVE-2017-5638)在當時造成莫大的騷動。但隨著時間過去,大家好像也都忘了有這麼一回事。不幸的是,這只是冰山一角,漏洞存在的根本原因並沒有被消除,因此還會有其他不為人知的漏洞出現,並帶來嚴重的後果。即使更新到最新的修補程式也不能保證Struts 2的應用程式安全無虞。在本文章中,我將會說明Struts 2為什麼很危險以及攻擊它的方式。
如下圖所示,左側第四列顯示程式碼執行(code execution)漏洞的數量,其實是Struts的,佔了將近33%。其他Java框架比重都沒有這麼多。而且它們的根本原因都在於框架架構本身。讓我們仔細看一下。
來源:CVE Details
Struts架構
Struts用兩個主要的架構解決方案:Value Stack和OGNL(Object Graph Navigation Language),將model、views和controllers結合在一起。Value Stack是應用程式在回應使用者請求時,用來存放那些物件(例如:應用程式配置、安全設置、數據等)的Stack。Value Stack中的物件使用OGNL進行操作,該語言是一種易於獲取(getting)和設置(setting)Java物件屬性(properties)的表達語言。
何謂OGNL
OGNL函式庫的功能為何?在下面範例中,先示範用OGNL呼叫特定類別的方法,再來看看OGNL可怕的地方。
金字塔頂部是OGNL表達式的入口。下面範例使用OGNL表達式"#session.user.name" 從session物件中取得使用者名稱,這是在Struts 2中合法使用OGNL的方法。但是,由於應用程式或框架本身存在漏洞,惡意資料可能在無意中被當作OGNL表達式執行。
金字塔的下一層是OGNL函式庫的框架實作。對我們來說最有趣的部分在於安全機制,因為安全機制定義了哪些OGNL表達式可以被執行。通過安全檢查後,該表達式則由Java OGNL函式庫執行。
成功攻擊的訣竅
從金字塔中可以得知,要在Struts 2應用程式中利用任意OGNL表達式進行程式碼執行(code execution)攻擊,需要兩個關鍵元素:
1. 找到執行OGNL表達式的注入點
2. 繞過安全機制
注入點
惡意OGNL表達式是如何進入的?
有兩種方式:直接注入,使用者輸入的資料,直接做為OGNL表達式的參數,或是加在參數的結尾。程式沒有經過檢查或消毒處理就直接執行。這就是臭名昭彰的CVE-5638-2017漏洞。下面這個範例將可能被污染的error message作為OGNL表達式執行,因此存在漏洞。
另一種注入方式是由多個OGNL表達式組成,因此很難找到。如下面的例子,第一個OGNL表達式用來填充變數,但尚未執行。緊接著,第二個OGNL表達式會先透過第一個OGNL表達式獲取污染的變數值並將其作為OGNL表達式執行。這種注入方式通常是無心的。舉例來說,開發人員常常需要動態取得某些資料,並結合到下一個OGNL表達式中執行。
繞過安全機制
現在已經了解注入點的長相,接著讓我們來看看如何繞過安全機制,成功注入精心準備的payload(惡意資料)吧!從攻擊者的角度來看,Struts 2早期版本2.3.14.1(2013)的安全機制還很陽春,隨著漏洞不斷被揭發,安全機制也不斷在升級,直到目前的版本2.5.20及2.3.37。最精采的部分在這裡,我們來研究這些payload是如何繞過當時版本的安全機制,而安全機制又是如何防堵一次次的攻擊。以下所有Payload的目標,都是先消除現有的限制再執行OS指令。
1. Payload 1
最初的安全機制限制了靜態方法呼叫(static method call)。由於某種原因,OGNL表達式可以存取該機制本身。下面這個Payload先允許進行靜態方法呼叫,然後使用OS命令呼叫靜態方法getRuntime().exec。
為了修正這個漏洞,2.3.14.2版本將allowStaticMethodAccess改為不可變。靜態方法這條路不能走了,那麼動態生成的物件呢?下一個Payload我們來試試用這個方法繞過安全機制。
2. Payload 2
為了修正這個漏洞,2.3.20版本禁止使用建構式,並導入了黑名單,限制可以用於OGNL的類別和套件。
3. Payload 3
安全機制不允許使用靜態方法,但允許使用靜態物件。攻擊者在OGNL函式庫中找到了一個靜態物件,其中包含預設狀態(零安全設置)的安全機制。以下Payload將目前的安全機制,設置為預設狀態並執行OS指令。
為了限制OGNL表達式不能存取ognl.MemberAccess和ognl.DefaultMemberAccess, 2.3.30版和2.5.2版將這兩個classes也加入黑名單中,剝奪了OGNL表達式對安全機制的存取權限。
4. Payload 4
OGNL可以在給定的context下呼叫任何物件的方法。利用這個特性,我們可以先取得黑名單所在的context,接著透過數個物件來取得兩個黑名單列表,並使用clear()方法將黑名單清空。之後,我們就能用先前Payload的技巧來執行OS指令。
為此,2.3.32版和2.5.10.1版先把com.opensymphony.xwork2.ActionContext加入黑名單,禁止OGNL使用,從而阻止了通往OgnlUtils的路徑。接著再把黑名單設為不可變的,使clear()方法無效。
5. Payload 5
ValueStack是一個錯縱複雜,牽連大量物件的物件。從ValueStack的根部開始,可以用多種不同的方式找到同一個物件。沒錯,我們的目標就是走另一條路取得OgnlUtils。另一方面,先前的修補雖然將黑名單設為不可變的,所以無法使用clear(),但是要清空列表仍可以使用setter進行更正。下面這個Payload包含兩個請求,第一個請求先清空黑名單,第二個再執行OS指令。分成兩個請求是為了獨立設定OgnlUtils,使其保持空的黑名單,直到應用程式重新啟動。
目前沒有CVE了,但漏洞可能依然存在
2.3.35和2.5.17版本修復了過往的缺陷,並暫停了Struts 2 OGNL注入的問題。不過因為架構的關係,黑名單勢必會被繞開,接著就看框架開發人員和攻擊者們,如何上演下一齣攻防戰。目前,該框架沒有已知的注入點和程式碼執行漏洞。但這並不會讓Struts 2的應用程式變得安全,因為開發人員可能會在無意中埋下注入點。例如,開發者在框架程式內,無意中嵌了一個良性的OGNL表達式,接著又在自己的程式碼中執行該OGNL表達式。以下範例說明注入點存在時,仍可運用最新版本的Struts 2應用程式。
首先,"createUser"這個Action建立User類的物件,並將其保存到session變數中。其中,"User"類的"isAdmin"屬性,預設值為false。
之後,"inject"這個Action從session中載入"User"物件,將使用者輸入做為OGNL表達式執行,再把"User"物件存回session。「getText()」方法是用於示範的注入點。
通過依序呼叫這些Action,攻擊者可以操縱未列入黑名單的物件。例如,將"User.isAdmin"設置為true,從而繞過授權檢查。
Struts 2開發人員應將這幾種漏洞類型牢記於心。建議廣泛使用SAST或IAST工具,像是Checkmarx,確實的尋找可能利用OGNL注入漏洞進行程式碼執行攻擊的弱點程式碼。如果沒有適當的應用程式安全測試流程,那麼開發人員寫下的注入漏洞,就有可能是由攻擊者幫忙找出來了。
Eugene擔任Checkmarx研究工作者長達8年之久,具有非常豐富的資訊安全經驗。在應用程式安全教育、網頁應用程式滲透測試、安全應用程式結構、安全組態和軟體開發方面,他都有非常深厚的技術知識和經驗。此外,在風險評估、稽核、意識計劃和遵循性方面也具有管理經驗。