作者:平安科技銀河實驗室
公眾號:https://mp.weixin.qq.com/s/wU1iacELbhspWMftRIQdeA
0x01 簡述
Dubbo是阿里巴巴一種開源的RPC服務框架,常被用來做分布式服務遠程對象的調用,日前Dubbo被發現有CVE-2020-1948的遠程代碼執行漏洞,官方針對這一漏洞發布了新版本Dubbo2.7.7,對源碼分析發現修復方式并不能阻止CVE-2020-1948漏洞利用。
Dubbo的安全威脅即使升級到了新版本依然存在威脅。不是繞過是官方修復錯了位置,并沒有產生新的0day。
0x02 補丁分析
針對爆發漏洞時間節點的commit記錄發現,官方對傳入參數做了類型的校驗,針對修復的commit記錄,我們做了詳細的分析。
2.1 時間線
Dubbo漏洞爆發的時間線如下圖所示:

2.2 修復代碼分析
對提交修復的commit代碼分析發現在DecodeableRpcInvocation中增加了輸入參數類型的校驗。Commits記錄截圖如下圖所示:

點開查看代碼,在原有的基礎上增加了參數類型的校驗,補丁代碼如下圖所示:

這里的parameterTypes是限制為Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;,如下圖所示;

本次poc傳入的參數類型為Class<?>類型。類路徑為Class<?>class com.rometools.rome.feed.impl.ToStringBean類,如果對參數檢查是可以有效阻止漏洞的利用,然而修復錯了地方。
從git的commit記錄來看,官方的修復思路推測是想在異常拋出前利用參數類型的校驗并攔截,防止引用的toString()方法造成代碼執行。
但是攔截錯了地方,通過對源碼的單步調試發現,此處拋出的異常并不在漏洞觸發的調用鏈上,具體的漏洞分析可見下文。
0x03 漏洞分析
CVE-2020-1948遠程代碼執行漏洞原理是遠程方法被動態調用導致的代碼執行。
下文將會對本次漏洞利用方式和觸發原理進行分析,因為本次代碼執行漏洞觸發原理與Java的反射機制相關,下文將簡單闡述Java的反射機制。
3.1 Java 反射機制
Java的反射機制是指在程序的運行狀態中,程序對于任意一個類都能知道這個類的所有屬性和方法;對于任意一個對象,都能調用它的任意一個屬性和方法;這種動態的信息獲取和調用機制被稱為Java的反射機制。
Dubbo本次的漏洞觸發原理就是toStringBean類內部動態調用外部傳入對象的方法導致的代碼執行。在研究漏洞的觸發前,需要先簡單了解為什么toStringBean類會在代碼中動態調用外部對象的方法。
3.2 toStringBean類初探
ToStringBean類是一個可以被序列化的公共類,可以將對象的類型和方法轉成字符串供調用。
在ToStringBean實現的toString方法中,會遍歷傳入對象的所有方法(Method對象),并且通過java實現的invoke方法動態調用傳入對象的所有Method對象。
toString實現方法如下圖所示:

3.3 漏洞觸發點
漏洞觸發點在com.rometools.rome.feed.impl.ToStringBean類的toString方法中,toString方法中的getter.invoke(obj, NO_PARAMS);語句,如下圖所示:

上圖是動態運行中的調試截圖,可以從圖中看出obj參數的值為JdbcRowSetImpl類的實例化對象,當此處for循環執行到JdbcRowSetImpl類中getDatabaseMetData函數時候,會調用函數內connect方法,導致執行JdbcRowSetImpl的執行鏈,導致代碼執行。
3.4 ToStringBean的調用鏈跟蹤
一個正常的dubbo的服務調用,當找不到注冊的service的時候會拋出異常,拋出異常的截圖如下圖所示:

這個inv是DecodeableRpcInvocation的實例化對象。上述紅箭頭中的+ inv語法會默認調用inv實例化對象的toString()方法,DecodeableRpcInvocation的toString()方法實現在父類Rpcinvocation中,跟進查看Rpcinvocation.java中的toString方法,截圖如下:

上圖中的argements方法根據右邊的變量調試信息顯示,實際上就是ToStringBean類的實例化對象,其中該對象有兩個參數,beanClass是Class<?>類型,obj是Object類型,此時的調用棧顯示obj是JdbcRowSetImpl實例化對象,beanClass是Class.ForName(“com.sun.rowset.JdbcRowSetImpl”)對象。Array.toString(Object[] obj)方法,將會在底層調用String.valueOf(Object obj),此時這個obj對象是帶有惡意負載的argements對象,繼續往下跟蹤Array.toString將argements向下執行,Array.toString(Object[] obj)方法如下圖所示:

上圖中可以發現傳入的對象最后會經過String.valueOf轉變為字符串儲存在b變量里,繼續往下跟蹤,在String.valueOf方法中,當傳入對象為toStringBean的時候,會調用toStringBean對象的toString()方法,String.valueOf方法如下圖所示:

當進入ToStringBean的toString()方法的時候,如之前漏洞觸發點所陳述,攜帶有惡意負載的對象,將會被執行惡意代碼。由此我們清楚的看到了調用鏈,如下:
throw new RemotingException ->RpcInvocation.toString()->Arrays.toString()->String.valueOf()->toStringBean.toString()->getter.invoke(obj, NO_PARAMS) 最終在ToStringBean的toString()方法中Invoke動態調用對象造成代碼執行。
3.5 緩解措施
一、內網服務
1、服務器防火墻添加內網IP白名單策略。 2、限制服務器的公網權限。
二、外網服務
1、漏洞被利用的類在rome-{version}.jar包中,公網服務可以考慮自查是否引用了rome-{version}.jar包,如果引用了可以考慮重寫toString()方法,重新編譯并加載到生產環境。
2、服務器防火墻添加IP白名單策略。
三、流量設備類監控
1、在流量檢測設備上可以增加檢測規則,通過分析發現流量中可能會包含以下危險類的關鍵字
(如:org.apache.xbean.naming.context.ContextUtil.ReadOnlyBinding, org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor, com.rometools.rome.feed, com.caucho.naming.QName, com.rometools.rome.feed.impl.ToStringBean等)。
下圖是復現過程中的流量抓包:

0x04 漏洞復現
用最新版本dubbo-2.7.7版本做漏洞復現,顯示最新版本仍存在CVE-2020-1948漏洞。 引用dubbo-samples的http的例子,git地址為https://github.com/apache/dubbo-samples,加載之后需要修改配置,將只支持http協議的方式修改為dubbo-rpc協議,maven相關配置如下圖所示:

啟用服務之后,利用傳統的jndi的利用方式,在公網啟用ldap服務和http服務,http服務目錄下放入我們需要加載的遠程執行代碼的class文件。
本文中漏洞復現選擇在windows本機上彈出計算器。
截至目前官方給出的最新版本依然存在該風險,利用如下圖所示:

4.2 利用方式詳解
本次漏洞復現的利用方式采用了JNDI遠程加載惡意類的方式。
本次選用的反射鏈是com.sun.rowset.JdbcRowSetImpl,jdk對于該鏈在較新的版本有限制,在做漏洞復現的過程中盡量使用低版本的jdk版本,如環境有限制需要配置特定的繞過策略。
本次dubbo的漏洞觸發點與fastjson有所不同,fastjson使用該鏈造成代碼執行是因為在setAutoCommit方法的時候,該方法中會使用this.connect()方法,connect()方法中可以使用jndi的方式加載惡意類造成代碼執行。
本次dubbo的利用方式雖然也是在connect()方法中被使用jndi的方式加載惡意類,但是dubbo是動態調用了JdbcRowSetImpl的getDatabaseMetaData方法,造成了connect方法被執行。
getDatabaseMetaData方法源碼如下圖所示:

從上圖可以看出getDatabaseMetaData對象中也調用了connect方法,connect方法中會加載dataSource指向的地址,被加載遠程Reference對象,造成JNDI方式的遠程代碼執行。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1264/