Dubbo漏洞分析

​ 之前审计的过程中,遇到过Dubbo这个组件,虽然知道这个组件存在反序列化漏洞,但是关于漏洞的详情和利用一概不知,所以下面对Dubbo的漏洞进行分析。

背景

​ Dubbo是一款开源的RPC和微服务治理的框架,最早是阿里开发的后来归到了Apache下面,支持多种协议,比如gRPC、Thrift、JsonRPC、Hessian2、REST、RMI、HTTP。

​ 下面是官网对于Dubbo架构的介绍。

image-20210909112048090

​ 主要包含消费者服务提供者注册中心监控中心容器等等。

image-20210909112713522

​ provider在启动时会将服务注册到注册中心,consumer在使用时会从注册中心获取到已经注册的服务,再根据consumer中配置的路由决定调用哪个服务,consumer通过RPC协议调用provider的服务并获取返回结果。

CVE-2019-17564

环境搭建

​ github下载官方提供的https://github.com/apache/dubbo-samples项目,导入simple-http。

image-20210909134909810

​ 直接下载的项目跑不起来,要更改http-provider.xmlhttp-consumer.xml的内容。

1
2
3
4
5
<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">

image-20210909135032905

​ 由于漏洞影响的版本为2.7.3所以要更改pom.xml的配置,注意是在dependency下面新增一个version标签,<version>2.7.3</version>

image-20210909135127465

​ 还有坑点,原本的配置是用${}包含配置文件的,但是低版本解析过程并没有解析${}中的内容,所以要进行更改。

image-20210909152940873

​ 更改后zookeeper更改后如下,portserver的参数更改类似。

image-20210909153148030

image-20210909153121415

​ 下面依次启动zookeeper,provider和Consumer即可,调试好后可以添加低版本的CC依赖,可以直观的看到利用结果。

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

漏洞分析

​ 在漏洞分析前我们还要再铺垫一些前置知识,provider在导出服务并注册到注册中心zookeeper时,实际上注册的是一个URL地址,但是这个URL并不是java.net.URL而是Dubbo自定义的URL,包含了下面几个属性。

  • protocol:一般是 dubbo 中的各种协议 如:dubbo thrift http zk

  • username/password:用户名/密码

  • host/port:主机/端口

  • path:接口名称

  • parameters:参数键值对

    ​ 下面是URL的一些栗子。

1
2
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&timestamp=1545721981946

​ 其中访问的path可以在dubbo service的bean配置中通过path属性进行设置,如果没有设置,则默认使用interface的名称当作path。

image-20210910105505997

​ 同样,由于Dubbo在进行RPC调用时支持多种协议,也可以在Bean配置中通过protocolname属性进行配置,由于CVE-2019-17564是Dubbo以http协议通信时出现的问题,因此我们这里配置也使用http的方式。

1
<dubbo:protocol name="http" id="http" port="8080" server="tomcat"/>

​ 通过上面的配置,假如我们要访问DemoService服务,可以通过下面的url访问。

1
http://127.0.0.1:8080/org.apache.dubbo.samples.http.api.DemoService

​ 现在我们再回到这个漏洞本身,通过官网的介绍Dubbo在实现http协议时使用的是spring的HttpInvoker来实现的。

image-20210915115832578

​ 我们再来了解HttpInvoker的介绍,其实现是基于标准的Java的序列化和反序列化机制,这里之所以要通过序列化和反序列化来实现,是因为在进行RPC调用时,调用的参数和返回值不一定是基本类型,也有可能是对象,而对象的传递肯定是要基于序列化和反序列,值得一提的是Dubbo在实现RPC调用时,会将我们要调用的方法和参数封装到RemoteInvocation对象中,服务端再接收到请求后,会将请求的内容反序列化为RemoteInvocation对象再去做其他操作,当我们了解了这些以后,即使不去看实现也能想到,在服务端provider接收到请求并进行反序列化时可能出现反序列化漏洞。

image-20210915120037799

客户端

​ 之前在分析Dubbo源码时已经分析了Http请求的处理过程,这里就不仔细分析了,把重点的代码说明一下即可。在org.springframework.remoting.httpinvoker.AbstractHttpInvokerRequestExecutor#executeRequest中,会对RemoteInvocation对象进行序列化。

image-20210915134000020

​ 在RemoteInvocation对象中封装了调用的方法名、请求参数类型、参数值等信息。

image-20210915134055775

​ 下面通过org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor#doExecuteRequest发送请求,prepareConnection主要完成请求发送前的一些设置。

image-20210915134326504

​ 在org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor#prepareConnection中可以看到通过POST发送请求。

image-20210915134410725

writeRequestBody中将序列化的RemoteInvocation对象作为post请求的内容发送给服务端。

image-20210915134603620

服务端

​ 当请求过来时,交给DispatcherServlet会将请求交给org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter#handleRequest处理,这个方法会通过readRemoteInvocation将request中的内容转换为RemoteInvocation对象。

image-20210915134928830

​ 获取request对象中的请求内容,在doReadRemoteInvocation中通过readObject反序列化,如果反序列化的对象不是RemoteInvocation对象则抛出异常。

image-20210915135352990

image-20210915135507598

​ 通过上面的分析,我们可以看出Dubbo Provider服务端在执行Consumer过来的请求时直接从POST中获取对象并进行反序列化,在反序列化之前并没有做其他安全的判断,所以我们以POST发送任意一个对象到Provider都会被反序列化,如果我们传入一个特殊构造的对象,Provider反序列化后则有可能造成命令执行或其他操作。

是不是一定要知道要调用的Provider提供服务的method和参数才能触发反序列化?

​ 这里需要了解服务导出的操作,我是以Tomcat做为服务端的,可以看到在构造Tomcat服务器时,添加了ServletMapping*,也就是所有的请求都会被我们注册的handler处理。

image-20210915140614106

​ 但是在org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle中会做如下处理。

image-20210915141019389

​ 首先获取请求uri并判断是否能找到对应的Exporter处理,如果找不到则会导致异常,SkeletonMap中保存的路径如果没有在provider的service元素中配置path属性,则默认使用interface的内容。

image-20210915141146132

image-20210915141347876

所以并不是访问服务端的任意接口都能触发漏洞的

漏洞复现

​ 漏洞复现就比较简单了,首先生成EXP

1
java -jar e:\tools\ysoserial.jar CommonsCollections4 "calc.exe" > c:\users\admin\desktop\payload.ser

​ 发送序列化的数据即可利用成功。 image-20210915151843418

漏洞修复

​ 在Dubbo 2.7.4 2.6.8 or 2.6.9修复了这个漏洞,我们看下漏洞的修复,首先看下服务导出的变化,在org.apache.dubbo.rpc.protocol.http.HttpProtocol#doExport中,不再使用HttpServiceExporter而是使用JsonRpcServer作为Exporter。

image-20210915155904494

​ 当我们请求服务时,也不再使用HttpServiceExporter处理请求而是使用JsonRpcBasicServer处理请求。

image-20210915161417835

handleNode中根据类型的不同做不同的处理。

image-20210915161808168

​ 从ObjectNode中获取参数和调用方法等信息。

image-20210915161849645

​ 得到要调用方法的method对象和参数值和实现类的代理对象后调用 invoke方法。

image-20210915162441189

​ 将参数类型做转换后通过反射完成调用。

image-20210915162626988

​ 这个过程中并没有涉及到JAVA本身的反序列化,不过还是涉及到JSON的反序列化操作,使用JACKSON进行反序列化,不过我并不熟悉JACKSON的漏洞,所以先放过吧。