之前審計的過程中,遇到過Dubbo這個組件,雖然知道這個組件存在反序列化漏洞,但是關於漏洞的詳情和利用一概不知,所以下面對Dubbo的漏洞進行分析。
背景Dubbo是一款開源的RPC和微服務治理的框架,最早是阿里開發的後來歸到了Apache下面,支持多種協議,比如gRPC、Thrift、JsonRPC、Hessian2、REST、RMI、HTTP。
下面是官網對於Dubbo架構的介紹。
主要包含消費者、服務提供者、註冊中心、監控中心、容器等等。
provider在啟動時會將服務註冊到註冊中心,consumer在使用時會從註冊中心獲取到已經註冊的服務,再根據consumer中配置的路由決定調用哪個服務,consumer通過RPC協議調用provider的服務並獲取返回結果。
環境搭建github下載官方提供的https://github.com/apache/dubbo-samples項目,導入simple-http。
直接下載的項目跑不起來,要更改http-provider.xml和http-consumer.xml的內容。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">由於漏洞影響的版本為2.7.3所以要更改pom.xml的配置,注意是在dependency下面新增一個version標籤,<version>2.7.3</version>。
還有坑點,原本的配置是用${}包含配置文件的,但是低版本解析過程並沒有解析${}中的內容,所以要進行更改。
更改後zookeeper更改後如下,port和server的參數更改類似。
下面依次啟動zookeeper,provider和Consumer即可,調試好後可以添加低版本的CC依賴,可以直觀的看到利用結果。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>漏洞分析在漏洞分析前我們還要再鋪墊一些前置知識,provider在導出服務並註冊到註冊中心zookeeper時,實際上註冊的是一個URL地址,但是這個URL並不是java.net.URL而是Dubbo自定義的URL,包含了下面幾個屬性。
dubbo://192.168.1.6:20880/moe.cnkirito.sample.HelloService?timeout=3000
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=1214&qos.port=33333×tamp=1545721981946其中訪問的path可以在dubbo service的bean配置中通過path屬性進行設置,如果沒有設置,則默認使用interface的名稱當作path。
同樣,由於Dubbo在進行RPC調用時支持多種協議,也可以在Bean配置中通過protocol的name屬性進行配置,由於CVE-2019-17564是Dubbo以http協議通信時出現的問題,因此我們這裡配置也使用http的方式。
<dubbo:protocol name="http" id="http" port="8080" server="tomcat"/>通過上面的配置,假如我們要訪問DemoService服務,可以通過下面的url訪問。
http://127.0.0.1:8080/org.apache.dubbo.samples.http.api.DemoService現在我們再回到這個漏洞本身,通過官網的介紹Dubbo在實現http協議時使用的是spring的HttpInvoker來實現的。
我們再來了解HttpInvoker的介紹,其實現是基於標準的Java的序列化和反序列化機制,這裡之所以要通過序列化和反序列化來實現,是因為在進行RPC調用時,調用的參數和返回值不一定是基本類型,也有可能是對象,而對象的傳遞肯定是要基於序列化和反序列,值得一提的是Dubbo在實現RPC調用時,會將我們要調用的方法和參數封裝到RemoteInvocation對象中,服務端再接收到請求後,會將請求的內容反序列化為RemoteInvocation對象再去做其他操作,當我們了解了這些以後,即使不去看實現也能想到,在服務端provider接收到請求並進行反序列化時可能出現反序列化漏洞。
客戶端之前在分析Dubbo源碼時已經分析了Http請求的處理過程,這裡就不仔細分析了,把重點的代碼說明一下即可。在AbstractHttpInvokerRequestExecutor#executeRequest中,會對RemoteInvocation對象進行序列化。
在RemoteInvocation對象中封裝了調用的方法名、請求參數類型、參數值等信息。
下面通過SimpleHttpInvokerRequestExecutor#doExecuteRequest發送請求,prepareConnection主要完成請求發送前的一些設置。
在SimpleHttpInvokerRequestExecutor#prepareConnection中可以看到通過POST發送請求。
writeRequestBody中將序列化的RemoteInvocation對象作為post請求的內容發送給服務端。
服務端當請求過來時,交給DispatcherServlet會將請求交給HttpInvokerServiceExporter#handleRequest處理,這個方法會通過readRemoteInvocation將request中的內容轉換為RemoteInvocation對象。
獲取request對象中的請求內容,在doReadRemoteInvocation中通過readObject反序列化,如果反序列化的對象不是RemoteInvocation對象則拋出異常。
通過上面的分析,我們可以看出Dubbo Provider服務端在執行Consumer過來的請求時直接從POST中獲取對象並進行反序列化,在反序列化之前並沒有做其他安全的判斷,所以我們以POST發送任意一個對象到Provider都會被反序列化,如果我們傳入一個特殊構造的對象,Provider反序列化後則有可能造成命令執行或其他操作。
是不是一定要知道要調用的Provider提供服務的method和參數才能觸發反序列化?
這裡需要了解服務導出的操作,我是以Tomcat做為服務端的,可以看到在構造Tomcat伺服器時,添加了ServletMapping為*,也就是所有的請求都會被我們註冊的handler處理。
但是在InternalHandler#handle中會做如下處理。
首先獲取請求uri並判斷是否能找到對應的Exporter處理,如果找不到則會導致異常,SkeletonMap中保存的路徑如果沒有在provider的service元素中配置path屬性,則默認使用interface的內容。
所以並不是訪問服務端的任意接口都能觸發漏洞的。
漏洞修復在Dubbo 2.7.4或2.6.8 or 2.6.9修復了這個漏洞,我們看下漏洞的修復,首先看下服務導出的變化,在HttpProtocol#doExport中,不再使用HttpServiceExporter而是使用JsonRpcServer作為Exporter。
當我們請求服務時,也不再使用HttpServiceExporter處理請求而是使用JsonRpcBasicServer處理請求。
handleNode中根據類型的不同做不同的處理。
從ObjectNode中獲取參數和調用方法等信息。
得到要調用方法的method對象和參數值和實現類的代理對象後調用 invoke方法。
將參數類型做轉換後通過反射完成調用。
這個過程中並沒有涉及到JAVA本身的反序列化,不過還是涉及到JSON的反序列化操作,使用JACKSON進行反序列化,不過我並不熟悉JACKSON的漏洞,所以先放過吧。
參考文章