Nexus Repository Manager 3 遠端程式碼執行漏洞 (CVE-2019-7238) 分析及利用
作者:iswin@360觀星實驗室
最近1年多基本上都在忙實驗室其它的工作,基本上很少關注JAVA安全這塊的內容,趕上正好週末休息,加上實驗室其它小夥伴對這個漏洞比較關注索性就抽出一點時間跟實驗室其它小夥伴一起研究了下,就當學習了,這套系統在一些大型的企業裡面其實還是有一定使用量的(尤其是在打點過程中),所以在實戰的滲透中還是有一些應用場景的。
漏洞簡介
這個漏洞在19年2月份左右被騰訊安全雲鼎實驗室發現並且提交Sonatype官方 https://cloud.tencent.com/developer/article/1390628 ,根據官方的通告和雲鼎披露來看基本上可以確定該漏洞是未授權的遠端程式碼執行。
經過研究發現該漏洞是一個基於OrientDB自定義函式的任意 JEXL 表示式執行漏洞,由於JEXL表示式可以執行JAVA程式碼同時沒有安全上的限制,所以間接的就成了遠端程式碼漏洞,這裡注意的是漏洞觸發條件對於新搭建的環境來說稍微有一點點坑,不過通過跟蹤程式碼應該可以看出一些規律。
漏洞分析
由於nexus的環境如果直接用原始碼在idea裡面編譯跑起來的話有點麻煩,依賴比較多,所以漏洞環境直接利用 docker 搭建,然後結合原始碼 nexus-public 使用jdwp進行遠端除錯即可。
根據漏洞描述和披露的利用截圖,我們很容易定位到漏洞的觸發位置,如下圖所示
根據函式的輸入我們可以構造如下資料包進行漏洞流程的跟蹤
然後在org.sonatype.nexus.coreui.ComponentComponent#previewAssets函數出打上斷點,跟蹤type引數發現nexus支援兩種型別的表示式Jexl和Csel(本質上就是Jexl表示式)
在最終執行表示式之前這兩種表示式會對錶達式的內容做校驗,主要是以下兩個程式碼片段
if (type == JexlSelector.TYPE) { jexlExpressionValidator.validate(expression) } else if (type == CselSelector.TYPE) { cselExpressionValidator.validate(expression) }
其實這個漏洞如果細心的人,在nexus後臺介面中會看到有個功能就是通過上述的那個介面,只不過哪裡的type是csel,起初我們先跟蹤了下csel表示式,但是發現csel表示式在校驗的時候做了一些限制,函式位置org.sonatype.nexus.selector.CselValidator#validate如下圖所示
如上圖紅圈標識的地方,在對錶達式語法進行檢查之後還對錶達式中解析後的函式和屬性的個數做了限制(大小為2)同時對解析後的內容做了限定,這樣一來csel表示式這條路明顯走不通了,就只能跟蹤jexl表示式了,jexl表示式的校驗比較簡單,只是對錶達式的語法進行了檢查,函式位置org.sonatype.nexus.selector.JexlSelector#JexlSelector,在表示式最後執行的時候會呼叫org.sonatype.nexus.selector.JexlSelector#evaluate,如下圖
到這裡表示式校驗和流程已經走完了,關於jexl表示式彈計算器隨便Google一下就知道了,例如 https://appcheck-ng.com/wp-content/uploads/2018/12/traccarexploit.v1.py.txt ,如果不去追究那些jexl表示式以及後續執行流程細節的話,如果你運氣好或許計算器已經彈出來了。
但是在萬事具備準備彈個計算器的時候,發現死活不行,最後在函式org.sonatype.nexus.selector.JexlSelector#evaluate上下斷點,發現也斷不下來,函式最後壓根都沒執行到這裡,到這裡除錯了很久,一度讓我以為漏洞的觸發點找錯了,但是在簡單回溯之後,發現這個點漏洞觸發概率相對較大一點,遂決定深入跟蹤分析下表達式後續的執行流程。
繼續斷點跟蹤,直接跟進org.sonatype.nexus.repository.browse.internal.BrowseServiceImpl#previewAssets函式,跟進這個函式我們繼續跟蹤jexlExpression變數的呼叫流程,最終我們可以發現表示式會被當做引數然後形成SQL由OrientDb進行執行,如下圖
如上圖所示,最終生成如下的SQL語句
SELECT FROM asset WHERE (bucket = #24:0 ) AND (contentExpression(@this, "iswin", "maven-central", {"maven-central": ["maven-central"]}) == true ) SKIP 0 LIMIT 300
到這裡我們基本上確定了表示式最後是有OrientDB進行執行了,這裡的contentExpression就是OrientDB的函式,Google搜尋了一下發現contentExpression函式並不是OrientDB的內建函式,那麼這個函式應該是nexus自定義的,關於OrientDB的內建函式以及自定義函式可以參考官方文件 https://orientdb.com/docs/last/SQL-Functions.html ,到這裡漏洞觸發的整個流程都清楚了,但是依然觸發不了,根據OrientDB自定義函式的例子,我們可以找到contentExpression函式最終的JAVA程式碼實現類位置org.sonatype.nexus.repository.selector.internal.ContentExpressionFunction,從流程上來看,最終的函式執行如下圖所示
到這裡理論上的函式呼叫流程已經梳理完了,但是依然觸發不了,這裡我們就需要考慮下是不是SQL語句的執行流程上出現了問題,回到SQL語句本身,如下所示
SELECT count(*) FROM asset WHERE (bucket = #24:0 ) AND (contentExpression(@this, "iswin", "maven-central", {"maven-central": ["maven-central"]}) == true )
當時看到這個語句的時候,我就在想這個語句的執行流程到底是怎麼樣的?是先執行SELECT count(*) FROM asset WHERE (bucket = #24:0 )這個語句還是說先執行AND後面的contentExpression函式,如果是先執行SELECT FROM WHERE,那麼會不會是由於這個語句執行完成之後沒有結果導致了contentExpression函式沒有被執行,如果是這樣那麼就簡單了,先登入後臺隨便在倉庫裡面傳一個檔案,這樣確保了SELECT count(*) FROM asset WHERE (bucket = #24:0 )語句有結果返回,傳送如下圖的Payload,成功觸發
至於OrientDB對語句的執行流程和函式的執行流程,有興趣的同學可以在以下截個函式上下斷點com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect#executeSearch、com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect#searchInClasses(1017行),決定是否去執行contentExpression函式的關鍵點在於com.orientechnologies.orient.core.sql.OCommandExecutorSQLSelect#executeSearch函式中target變數是否為null,當target變數不為null的時候才回去執行fetchFromTarget(target)函式從而去觸發OrientDB的自定義函式,如下圖
這裡就引出了我開篇說道到的在除錯的時候一個坑,也反映了漏洞觸發的前置條件,即確保被攻擊系統倉庫中有專案(當然這個要求大部分都能滿足,但是對於搭漏洞環境除錯人員來說的確非常坑)。
漏洞利用
當然瞭如果只是純分析這個漏洞,那麼上面已經花了大量時間來說了,那麼這裡主要討論下在實際攻防環境下的利用,一般情況下我們大部分的攻擊環境分為兩種,一種是被攻擊機機器能出網,另外一種是不能出網的情況。
針對出網的環境我們可以直接彈個SHELL,然後就搞定了,這裡我們主要討論不能出網的環境,針對不能出網的環境環境主要有一下兩種利用辦法(當然還有其他的奇技淫巧,這裡不做討論)
- 寫WEBSHELL檔案
- 執行命令(需要回顯)
在這裡針對nexus系統來說,且不說能不能解析jsp或者jspx指令碼,但凡能寫目錄都是root許可權(docker環境,其它實際部署環境沒考證),但是漏洞執行的點為nexus許可權,所以寫shell基本上沒戲,況且nexus不能解析指令碼檔案,那麼現在重點就討論該漏洞如何回顯的問題。
這裡就來討論下針對JAVA WEB系統存在遠端程式碼執行時,如何進行回顯的問題,這類的系統例如Struts2、反序列化漏洞等,那麼針對表示式類的程式碼執行,無非就以下幾種主流的方法
- 獲取到HTTP請求Reponse的物件,然後獲取輸出流然輸出
- 利用異常機制進行報錯回顯(最早在我們內部Weblogic、Websphere等反序列漏洞的回顯就是基於此原理)
- 通過命令執行把結果放到WEB目錄(比如TXT,針對指令碼不能解析的情況,例如Jeecms的漏洞)
當然對於nexus系統來說2、3明顯是不可行的,那麼對於1這種方案到底行不行,一開始的時候我經過一番探索,發現這種環境下回顯是不可能的,主要在於jexl表示式的類載入器無法載入到Servlet相關的類,我通過主動類載入的方式載入了相關類,同時我也找到了一個類提供了一個靜態方法獲取request和response,函式位置com.softwarementors.extjs.djn.servlet.ssm.WebContextManager.get(),但是一直獲取到的是null,所以這種方法就放棄了。
從另外一個維度來考慮下JAVA中介軟體(jetty)是怎麼處理請求的,針對每個請求中介軟體會單啟動一個執行緒來處理,針對這個請求的引數之類的會繫結到當前執行緒上,這裡我做了個實驗,如下圖所示
這裡會發現在nexus的程式中jexl的表示式的執行緒和webapp的執行緒是同一個執行緒,那麼這裡在jexl表示式中就有可能獲取到webapp請求中的一些變數,這裡思路已經說完了,至於怎麼去獲取response物件,這裡直接給出提示,可以直接Debug跟蹤類java.lang.Thread中threadLocals變數,至於怎麼實現回顯,仁者見仁智者見智了,大家可以去除錯下。
這裡給出最終的利用截圖
參考
- https://cloud.tencent.com/developer/article/1390628
- https://orientdb.com/docs/last/SQL-Functions.html