Qu’est-ce que Java RMI ?
RMI, pour Remote Method Invocation, est une API disponible dans Java core depuis la JDK 1.1 qui permet d’appeler des méthodes distantes afin d’invoquer / d’accéder à des objets qui sont exécutés dans une JVM différente de celle de l’objet appelant.
C’est une communication client / serveur utilisant deux classes principales :
- Le stub : classe côté client. C’est une représentation de l’objet distant chez le client.
- Le skeleton : classe côté serveur. Il s’agit de l’objet qui réside du côté serveur.
Le stub et le skeleton communiquent ensemble pour transmettre la demande à l’objet distant et se chargent d’assurer les mécanismes d’appel, de communication, d’exécution, de renvoi et de réception du résultat.
Afin de créer un objet distant et de l’appeler via RMI, plusieurs étapes sont nécessaires. Au niveau du serveur, il est nécessaire de :
- Définir une interface qui contiendra les différentes méthodes qui pourront être appelées à distance
- Développer la classe qui implémentera l’interface de l’objet distant
- Écrire la classe qui permettra d’instancier et d’enregistrer l’objet en lui affectant un nom dans l’espace de nom RMI (RMI Registry)
Au niveau du client, il faut :
- Obtenir une référence de l’objet distant à partir de son nom
- Appeler la méthode à partir de cette référence
Nous allons utiliser cette API pour attaquer des services JMX.
A quoi correspond JMX ?
JMX, pour Java Management eXtension, est une spécification du standard Java, proposant une API permettant de gérer, contrôler et surveiller différents composants Java, que ce soit des applications, des services ou même la JVM elle-même. RMI n’est aujourd’hui plus utilisé pour développer de nouvelles applications (on lui préfère REST et SOAP aujourd’hui pour communiquer avec des services distants) , en revanche cela reste le standard utilisé permettant le monitoring à distance via JMX.
De manière générale, JMX est utilisé par les administrateurs pour superviser différentes applications Java, cependant, JMX peut également être utilisé afin d’invoquer des méthodes sur le système pour, par exemple, obtenir des informations sur le système d’information (charge, uptime, etc.).
Pour se connecter à un service JMX via RMI, on peut utiliser la jconsole qui fait partie intégrante du JDK.
Quelques définitions
MBean
Afin de gérer des ressources, JMX définit ce qu’on appelle un MBean (Managed Bean). Un MBean est une classe Java dont le rôle va être d’assurer la communication avec la ressource à gérer. Cette classe doit suivre certaines règles de conception du standard JMX, à savoir :
- Implémenter une interface ;
- Posséder à minima un constructeur public ;
- Être une classe publique concrète ;
- Respecter certaines conventions de nommage (le constructeur doit avoir le nom de la classe MBean suffixé de MBean par exemple).
L’interaction avec ces MBean se fait via JMX.
MLet
Un MLet, pour Management Applet, est un service qui va permettre de charger un MBean, qu’il soit local ou distant. On pourra ensuite l’instancier et l’enregistrer au niveau du serveur de MBean pour l’utiliser en tant que MBean. Il va ensuite lire un fichier texte contenant la description des MBean à traiter (ce fichier est fourni au service sous la forme d’une URL et peut être local ou distant).
Chaque MBean au sein de ce fichier MBean est défini via un tag <MLET>, qui se présente sous la forme suivante :
<MLET
CODE = class | OBJECT = serfile
ARCHIVE = "archiveList"
[CODEBASE = codebaseURL]
[NAME = mbeanname]
[VERSION = version]
>
[arglist]
</MLET>
Une fois ce fichier lu, le MBean décrit est traité par le service JMX.
Les attaques sur les services JMX
Plusieurs attaques sur les services JMX existent et ne sont pas forcément récentes. En revanche, ces services étant souvent déployés sur des environnements internes avec une configuration par défaut, ils sont régulièrement vulnérables.
L’attaque la plus simple consiste à utiliser les Mbean déjà déclarés au sein du serveur de Mbean. Ceux-ci peuvent permettre l’accès en lecture à des options de configuration qui peuvent contenir des identifiants de connexion en clair, voire des classes permettant l’exécution de code sur le système sous-jacent.
Une seconde attaque, nommée CVE-2016-3427, est une attaque de désérialisation Java et nécessite que le classloader de l’application supervisé charge un gadget qui pourra être exploité.
Enfin, un dernier type d’attaque consiste à charger un Mbean malveillant à distance via le Mlet. C’est cette dernière attaque qui va nous intéresser.
Cette attaque nécessite deux prérequis principaux :
- Le service JMX ne doit pas être protégé par une authentification (configuration par défaut). En effet, c’est cette condition qui permettra d’invoquer la classe getMBeansFromUrl et ainsi de charger un Mlet distant.
- Le serveur sous-jacent doit permettre les connexions sortantes vers un serveur distant que vous contrôlez, que ce soit sur internet ou sur le réseau local, sur lequel sera déployé notre MLet malveillant.
En effet, lorsque l’authentification n’est pas activée, un client distant peut créer un MLet qui pourra être utilisé pour enregistrer un MBean sur le serveur depuis une URL distante.
Les étapes de l’attaque sont les suivantes :
- Démarrer un serveur web sur lequel sera hébergé le MLet et le MBean malveillant au sein d’un JAR.
- Le JAR contenant le MBean malveillant va être chargé par le service JMX et devient disponible à l’attaquant.
- Invoquer la méthode getMBeansFromURL disponible via le MLet. L’URL passée en argument est celle hébergeant le MBean malveillant. Le service JMX va ensuite se connecter à cette URL et lire le contenu du fichier MLet.
- Le JAR contenant le MBean malveillant va être chargé par le service JMX et devient disponible à l’attaquant.
Ce processus est décrit au sein de la capture suivante. L’outil beanshooter, implémentant la plupart des attaques sur les services JMX, a été utilisé :
Une fois le MBean malveillant chargé, l’attaquant peut exécuter des commandes arbitraires sur le serveur sous-jacent :
Note : Dans le cadre de l’exemple ci-dessus, un connecteur JMXMP (JMX Message Protocol) a été attaqué. Il est donc nécessaire de spécifier l’option –jmxmp lors de l’utilisation de beanshooter. Pour se connecter à ce type de service via la jconsole, il est nécessaire de disposer du JAR opendmk_jmxremote_optional_jar-1.0-b01-ea.jar.
Contre-mesures
Pour se prémunir de ce type d’attaques, le plus simple reste encore d’activer l’authentification au sein du service JMX. En effet, l’authentification n’engendre pas uniquement la nécessité d’avoir des identifiants valides pour accéder au service, cela rend également l’invocation de la méthode getMBeansFromURL impossible dans les configurations par défaut.
On peut également rappeler la nécessité de mettre en place des règles de filtrage réseau afin de n’exposer ce type de services d’administrations qu’a une population restreinte d’utilisateurs (aux administrateurs des applications Java par exemple).
Références
- https://mogwailabs.de/en/blog/2019/04/attacking-rmi-based-jmx-services/
- https://github.com/qtc-de/beanshooter
- https://www.jmdoudoux.fr/java/dej/chap-jmx.htm
- https://www.jmdoudoux.fr/java/dej/chap-rmi.htm
- https://jmdoudoux.developpez.com/cours/developpons/java/chap-rmi.php
- https://github.com/mogwailabs/mjet
- https://mvnrepository.com/artifact/org.glassfish.external/opendmk_jmxremote_optional_jar/1.0-b01-ea
- https://docs.oracle.com/cd/E19698-01/816-7609/connectors-116/index.html
- https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/com/sun/jmx/remote/security/MBeanServerAccessController.java#L619