作者:騰訊安全云鼎實驗室
公眾號:https://mp.weixin.qq.com/s/jKM-Z2BTFfk_Ro1rJAxg5w

北京時間2020-6-22日Apache官方發布了Dubbo 2.7.7版本,其中修復了一個嚴重的遠程代碼執行漏洞(CVE-2020-1948),這個漏洞是由騰訊安全玄武實驗室的ruilin提交,該漏洞允許攻擊者使用任意的服務名和方法名發送RPC請求,同時將惡意序列化參數作為有效載荷,當惡意序列化的參數被反序列化時將執行惡意代碼。該漏洞與 CVE-2017-3241 RMI反序列化漏洞有點類似,都是在遠程調用過程中通過方法參數傳入惡意序列化對象,服務端在解析參數進行反序列化時觸發。Dubbo Github Star數量32.8k,知名度不亞于fastjson,被大量企業使用,包括一些知名互聯公司,漏洞影響十分廣泛。

補丁分析

從補丁對比文件來看,在DecodeableRpcInvocation.java文件133-135行增加了對Method方法進行驗證,如果驗證不通過則拋出非法參數異常終止程序運行,核心代碼代碼如下:

if (!RpcUtils.isGenericCall(path, this.getMethodName()) 
&& !RpcUtils.isEcho(path, this.getMethodName()))
{throw new IllegalArgumentException
("Service not found:" + path + ", " + this.getMethodName());}

跟進isGenericCall和isEcho方法,發現驗證邏輯十分簡單,如果method等于$invoke$invokeAsync或者$echo則返回true。不得不說此處站在開發角度思考是沒問題的,非Dubbo自帶service中的$invoke$invokeAsync$echo方法以外,其他函數名全部拋出異常,但是萬萬沒想到RPC調用過程中方法名是用戶可控的,所以攻擊者可輕易的將method設置為其中任意一個方法來繞過此處限制。

public static boolean isGenericCall(String path, String method) 
{return "$invoke".equals(method) || "$invokeAsync".equals(method);}
public static boolean isEcho(String path, String method)
 {return "$echo".equals(method);}

通過對歷史版本的回溯,發現在2019.10.31日的一次提交中DubboProtocol類的getInvoker函數的RemotingException代碼塊中增加了getInvocationWithoutData方法,對inv對象的arguments參數進行置空操作,用來緩解后反序列化攻擊,此處正是CVE-2020-1948漏洞后反序列化利用的觸發點。

如下getInvocationWithoutData函數,可能是為了方便開發者排查問題,如果系統配置log4j debug級別或者不配置任何其他級別,則不會將inv對象的arguments參數設置為null,會直接返回invocation對象,所以還是存在被后反序列化漏洞攻擊的風險。所謂后反序列化簡單理解就是漏洞是在對象被正常反序列化之后觸發,比如在異常處理中對成功反序列化的對象進行間接或直接的函數調用,從而導致的代碼執行。

/*** only log body in debugger mode for size & security consideration.
   *
   * @param invocation
   * @return
   */
private Invocation getInvocationWithoutData
(Invocation invocation) 
{if (logger.isDebugEnabled()) {return invocation; }
if (invocation instanceof RpcInvocation)
{RpcInvocation rpcInvocation = (RpcInvocation) invocation;
rpcInvocation.setArguments(null);
return rpcInvocation;}
return invocation; }

由上可知,DecodeableRpcInvocation#decode請求體解碼函數的驗證邏輯存在繞過DubboProtocol#getInvocationWithoutData函數的后反序列緩解存在無效情況。

構造POC

知道了method的驗證邏輯,修改CVE-2020-1948 Poc中的的service_name和method_name參數的值,分別為:org.apache.dubbo.rpc.service.GenericService和$invoke。

在DecodeableRpcInvocation類中的decode函數方法起始處設置斷點進行Debug。

代碼123-124行首先通過path(對應客戶端的service_name)參數來獲取服務描述對象repository,該對象包含了服務名、接口類型和方法信息等。

繼續跟進,由于params是我們構造Gadget, 最終repository對象獲取到函數描述對象為null。

繼續跟進,由于pts變量沒有被賦值,所以pts== DubboCodec.EMPTY_CLASS_ARRAY表達式成立, 接著進入isGenericCall函數,由于rpc調用設置的method的值為$invoke,此處可以驗證通過。

最后進入hession反序列化流程,成功執行了代碼。

可以看到調用棧如下:

另外,Dubbo暴露的端口如果開啟了Telnet協議,攻擊者可以連接到服務通過ls命令查看service、method等信息,甚至可以執行shutdown危險操作直接關停服務。白帽子@CF_HB在Dubbo 2.6.8版本通過Telnet服務中InvokeTelnetHandler.java類在處理invoke命令時的漏洞結合Fastjson反序列化漏洞成功進行了利用。隨著越來越多的安全研究人員對Dubbo安全問題的關注,相信后面會有更多的漏洞被挖掘出來。

漏洞的修復

1.由社區aquariuspj用戶給出的對DecodeableRpcInvocation增加入參類型校驗

2.漏洞發現者ruilin建議刪除RpcInvocation類的toString方法中輸出的arguments參數,防范后反序列化攻擊。同時對Hessian進行黑白名單加固來防范Hessian反序列化攻擊。

目前官方和社區給出的修復方法都是單點防御,很容易被攻擊者繞過,短期防護可參考玄武實驗室給出的方案:

  • 出網限制

經研究當前存在的反序列化利用鏈大多需要遠程加載惡意類,如果沒有特殊需求,建議在不影響業務的情況下將服務器配置出外網限制。

  • IP白名單

建議用戶將能夠連接至Dubbo服務端的消費端IP加入到可信IP白名單里,并在服務端配置可信IP白名單,以防止攻擊者在外部直接發起連接請求。

  • 更換默認的反序列化方式

Dubbo協議默認采用Hessian作為序列化反序列化方式,而Hessian存在危險的反序列化漏洞。用戶可以在考慮不影響業務的情況下更換協議以及反序列化方式,如:rest,grpc,thrift等。

  • 關閉公網端口

不要將Dubbo服務端的開放端口暴露在公網,但需要注意這種場景若攻擊者在內網環境仍然可以進行攻擊。

參考

Apache Dubbo Provider 遠程代碼執行漏洞 (CVE-2020-1948)
https://xlab.tencent.com/cn/2020/06/23/xlab-20-001/

Java“后反序列化漏洞”利用思路
http://rui0.cn/archives/1338

[CVE-2020-1948] Apache Dubbo Provider default deserialization cause RCE
https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1263/