THM - Rabbit
Enumération initiale
On commence par un scan nmap
1 | PORT STATE SERVICE VERSION |
On voit qu’il y’a 4 ports ouverts :
- 22 (
SSH
) - 80 (
HTTP
) - 4369 (
EPMD
) - 25672 (
Erlang Distribution
)
Le scan nmap
nous indique que le port 80 redirige vers http://cloudsite.thm
donc on ajoute cloudsite.thm
à notre fichier hosts ce qui nous permet d’accéder au site web.
En testant les différentes fonctionnalités du site, on s’aperçoit que le bouton permettant de se connecter nous rediriger vers http://storage.cloudsite.thm/
. Ainsi, pour y accéder, on ajoute ce sous domaine au fichier hosts ce qui nous permet d’accéder à une page d’authentification.
Accès privilégié à l’interface web
Après avoir créer un faux compte, on obtient une page nous indiquant que le service n’est accessible que pour les utilisateurs internes.
En analysant les cookies, on découvre un token JWT.
Le décodage du token permet de voir que pour accéder à l’application, il faut que le champ soit subscription
contienne ‘active’.
Malheureusement, après avoir tester les attaques les plus connus sur les jwt, aucune ne fonctionne et on ne peut pas accéder à l’application de cette manière.
En revanche, lors de la création d’un compte, on remarque que l’envoie des données et envoyé en clair et correspond exactement à certains champs du payload du token JWT.
On peut alors tenter de faire une attaque de type Mass Assignement
. Ce type d’attaque se produit lorsqu’une application ne filtre pas correctement les données reçues depuis le client et accepte des champs supplémentaires non prévus
. Lors d’une inscription classique, l’utilisateur envoie généralement son email et son mot de passe. Cependant, si le serveur mappe automatiquement l’intégralité du contenu reçu dans son objet interne sans vérifier les champs autorisés, un attaquant peut injecter des champs supplémentaires.
Ainsi, dans notre cas on peut ajouter "subscription": "active"
dans la requête d’inscription ce qui permet de contourner la protection actuellement mise en place. Cette faille est fréquente lorsque les frameworks réalisent automatiquement le binding
des données JSON (comme Node.js, Ruby on Rails, Django, etc…) sans restreindre explicitement la liste des champs modifiables.
La modification des champs d’un utilisateur de la façon présentée précédemment permet d’accéder au site de façon complète.
API Docs via SSRF
Après s’être fait passé pour un utilisateur interne, on tombe sur une interface d’upload. Cette dernière permet d’uploader un fichier sur le site web directement en uploadant un fichier sur le site ou en le récupérant depuis une URL.
On peut alors tenter d’accéder à certains endpoint de l’api en exploitant une potientiele SSRF avec l’upload par URL. Pour ce faire, on commence par énumérer les différents endpoint de l’api:
1 | ffuf -w /usr/share/wordlists/dirb/big.txt -u 'http://storage.cloudsite.thm/api/FUZZ' |
Comme on a pas accès directement aux endpoints et en particulier à la documentation, on peut essayer d’y accéder en exploitant l’upload de file par URL. Comme le port utilisé par l’API n’est généralement pas le même que celui utilisé par le front, il faut d’abord trouvé le port utilisé par l’API. Pour réaliser cela, on peut faire un script qui indique si un port est ouvert ou non en local en se basant sur la génération d’un url pour accéder au fichier ou non lors de l’upload par url avec 127.0.0.1.
Ainsi, en laissant tourner le script, on trouve que le port 3000 est ouvert en local (on pouvait aussi le deviner car l’application utilise express qui utilise le port 3000 par défaut)
On peut alors essayer d’accéder à l’endpoint docs
de cette maniere :
Cela nous permet d’obtenir la documentation complète de l’API :
SSTI to RCE
Avec la documentation, on identifie l’endpoint fetch_messege_from_chatbot
qui est en cours de développement. En essayant d’y accéder sans paramètre. Le serveur nous indique qu’un paramètre username
est nécessaire au fonctionnement de l’endpoint.
En spécifiant un username, on voit que le message qui est renvoyé par le message semble être générique et peut laisser supposer que ce dernière utilise un template.
On teste alors certains payload pour vérifier la présence ou non d’une SSTI
, et on voit qu’un des payload fonctionne bien car il est interprété.
Ainsi, on peut obtenir un reverse shell sur la machine qui utilise Jinga
en envoyant le payload suivant :
1 |
|
Shell as rabbimq
Maintenant que l’on a obtenu un shell, on peut lire les différents fichiers sensibles de rabbitmq et en particulier le fichier .erlang.cookie
et /etc/rabbitmq/rabbitmq-env.conf
1 | azrael@forge:~/chatbotServer$ cat /var/lib/rabbitmq/.erlang.cookie |
1 | azrael@forge:~$ cat /etc/rabbitmq/rabbitmq-env.conf |
Le fichier .erlang.cookie
contient le cookie Erlang utilisé par les nœuds du cluster RabbitMQ pour s’authentifier entre eux. Ce cookie agit comme une clé secrète de session entre les nœuds distribués Erlang. En possession de ce cookie, il est possible de démarrer un shell Erlang avec la même clé de session et ainsi établir une communication directe avec le nœud rabbit@forge
déjà existant. Grâce à cette authentification inter-nœuds, nous pouvons exécuter des commandes à distance sur le processus Erlang du serveur RabbitMQ en utilisant les fonctions internes comme net_adm:ping
ou rpc:call
.
Ainsi, en s’authentifier, on peut exécuter des commandes en tant que rabbitmq et récupérer un reverse shell.
1 | azrael@forge:~$ erl -sname attacker -setcookie RsudLp8JbYOF9Lfm |
Rabbitmq to root
Ensuite, afin d’identifier les comtes utilisateurs enregistrés dans RabbitMQ, on peut utiliser la commande rabbitmqctl list_users
. Cette commande permet d’afficer la lsite des utilisateurs présents ainsi que leurs tags associés. Ici, cela permetd e découvrir l’existence d’un compte root
.
1 | rabbitmq@forge:~$ rabbitmqctl list_users |
La petite notice nous indique que le mot de passe du compte root du serveur est égal au hash SHA-256 du mot de passe du compte root de RabbitMQ. Ainsi, pour récupérer ce mot de passe, on peut utiliser la commande suivante ce qui vas permettre d’exporter la configuration et d’obtenir un hash du mot de passe.
1 | rabbitmq@forge:~$ rabbitmqctl export_definitions /tmp/rabbitmq_config.json |
D’après la documentation, RabbitMQ stocke les mots de passe de cette manière :
base64( 4bytes SALT + SHA-256 ( 4 bytes SALT + password))
Ainsi, pour obtenir le hash SHA-256 du mot de passe, il suffit de réaliser la transformation inverse avec la commande suivante :
1 | ┌──(kali㉿kali)-[/opt] |
Ce hash permet de compromettre le compte root :