De nos jours, les exécutions de code à distance ou RCE (Remote Code Execution) font partie des attaques web les plus redoutées. Les risques qui en découlent sont critiques, car ce type de vulnérabilité permet en somme une prise de contrôle du système et d’être présent au sein du réseau de la victime (selon le contexte du serveur : dédié, mutualisé, interne etc.).
Au sein de cet article, il sera expliqué :
- Ce qu’est un interpréteur de commandes interactif et pourquoi un attaquant nécessite ou préfère son utilisation lorsqu’il dispose d’une vulnérabilité de type RCE.
- S’en suivra une analyse de deux manières d’obtenir un interpréteur en ligne de commandes interactif avec une RCE et des options existantes pour contraindre son obtention à un attaquant.
- Enfin, il sera expliqué une méthode spécifique appelée Forward shell qui peut être mise en place lorsque les deux autres font défaut.
Introduction
Contexte et termes
Avant de rentrer dans le vif du sujet, remettons quelques bases et un peu de contexte. Notre article va aborder les post-exploitations dans le contexte de failles applicatives. En effet, lorsqu’un attaquant parvient à exploiter une vulnérabilité au sein d’une application web, son objectif est très souvent d’obtenir un interpréteur de commandes sur l’hôte compromis afin de gagner en confort ou parce qu’il le nécessite.
Ce document est la propriété du cabinet XMCO. Toute reproduction est strictement interdite.
Pour mieux comprendre ce qu’est un interpréteur de commandes, il convient d’expliquer les termes techniques suivants :
Terme | Définition |
Terminal | Un terminal est un élément matériel permettant à un utilisateur d’interagir avec un hôte. Il s’agit souvent d’un clavier associé à un écran de texte. Actuellement, ils sont souvent «émulés». |
Tty (TeleTYpe) | Sur les systèmes Linux, tout fonctionne à l’aide de fichiers. Le fichier représentant un terminal est appelé un fichier tty. |
Interprète de commandes | Un interprète de commandes est un programme qui interprète les commandes d’un utilisateur via un terminal pour les envoyer au système d’exploitation sous-jacent. |
Invité de commandes | Interface utilisateur permettant d’insérer des commandes à la suite. |
Émulateur de terminal | Un émulateur de terminal est un programme qui émule un terminal physique comme iTerm ou SSH. |
Ainsi, lorsqu’un invité de commandes est affiché sur un système Linux, c’est en réalité un émulateur de terminal, connecté à un terminal virtuel, identifié par un fichier tty, à l’intérieur duquel tourne un interpréteur de commandes.
Note : Les exemples et principes présentés ne seront spécifiques qu’aux systèmes Linux.
Un peu d’histoire…
C’est en 1960 qu’apparaissent les premiers périphériques permettant d’envoyer et recevoir des caractères à un ordinateur. Avant leur apparition, les données étaient enregistrées pour être ensuite envoyées par exemple via une carte perforée. Aucune communication n’intervenait alors entre l’Homme et la machine.
Des systèmes d’exploitation complets ont ensuite été développés tels que MS-DOS, le prédécesseur de Windows. Il fonctionnait avec une interface graphique affichant seulement l’invité de commandes que l’on connait aujourd’hui sous le nom de CMD, mais qui se nommait avant COMMAND.
De la même manière, Linux développait également ses propres interfaces en ligne de commandes avec l’arrivée du Bourne shell (bsh) en 1977 qui sera remplacé par le Bourne-Again shell (bash) en 1989.
Ces différents interpréteurs de commandes permettent l’interprétation de chaque ligne saisie afin de la traduire dans un langage adapté au système d’exploitation qui pourra ensuite exécuter la commande.
Historiquement, ces interfaces en ligne de commandes ont été conçues afin d’être utilisées en local.
L’utilisateur souhaitant exécuter des commandes directement sur sa machine devait alors avoir un accès physique à celle-ci.
Avec l’arrivée des protocoles SSH et Telnet, il est devenu possible de communiquer simplement à distance avec un serveur via une interface de commandes, mais ils nécessitent souvent d’être authentifiés ou ne sont parfois simplement pas présents nativement sur les systèmes. Un attaquant doit donc être en mesure de trouver d’autres solutions pour réaliser cet échange distant afin d’exécuter des commandes sur un serveur.
Nous allons donc étudier les notions de web shell non interactif jusqu’à expliquer les concepts de Bind shell, reverse shell et Forward shell grandement utilisés dans le cadre de nos tests d’intrusion.
Web shell non interactif
Afin de comprendre pourquoi un attaquant nécessite un interpréteur en ligne de commandes interactif lorsqu’il est parvenu à identifier une RCE, commençons par définir le principe d’interactivité.
Un interpréteur de commandes interactif permet d’exécuter des commandes à la suite et d’obtenir leur sortie via un invité de commandes.
Il est ensuite fondamental d’expliquer comment les commandes sont interprétées et exécutées par le serveur sous-jacent lors de l’exploitation d’une RCE.
Pour ce faire, prenons le code PHP vulnérable ci-dessous :
Ce code correspond à la page web suivante :
Le formulaire prend en entrée une adresse IP renseignée par l’utilisateur et la fait passer dans la fonction PHP shell_exec
(https://www.php.net/manual/fr/function.shell-exec.php).
Cette dernière va exécuter la commande ping à l’adresse communiquée directement via l’interprète de commandes du serveur Linux hébergeant l’application web.
Cependant, l’entrée utilisateur n’est pas assainie avant d’être insérée dans la fonction et un attaquant peut donc échapper au contexte initial afin d’exécuter les commandes de son choix :
L’application web est ainsi vulnérable à une RCE et l’attaquant peut exécuter la commande de son choix directement sur le serveur.
Ici, la commande exécutée par le serveur est la suivante :
bash -c ‘ping -c 1 127.0.0.1; id’
Ce que nous obtenons est donc un invité de commandes qui appelle l’interpréteur de commandes Bash.
Ceci est un exemple particulier d’exploitation d’exécution de commandes à distance. Le contexte permettant la vulnérabilité changera selon l’architecture de l’application. De ce fait, la manière de l’exploiter variera également. Le résultat de la commande ne sera pas toujours affiché comme ici par exemple.
Cependant, le principe d’échapper au contexte initial d’une fonction pouvant exécuter des commandes afin d’injecter du code malveillant restera fondamentalement le même. À chaque commande envoyée via le formulaire, l’interpréteur de commandes bash est appelé afin de l’exécuter.
Cela peut se traduire par la suite d’actions suivante :
À l’aide de ce diagramme, il est déjà possible de constater l’un des problèmes majeurs que peut rencontrer un attaquant lors de l’exploitation d’une RCE. Chaque commande envoyée sera exécutée au sein d’un nouveau processus d’interpréteur de commandes.
Ainsi, en exécutant la suite de commandes suivante :
L’application retournera le répertoire actuel de l’application sans jamais varier :
Cela peut donc contraindre un attaquant qui souhaite se déplacer librement sur le système afin d’y consulter les différents fichiers. La seule manière de contournement est de chainer les commandes de la manière suivante :
L’interpréteur de commandes appelé via l’invité de commandes web n’est donc pas interactif, car il n’est pas possible d’interagir avec celui-ci avec plus d’une commande à la suite.
Un autre problème majeur concerne les commandes nécessitant une entrée utilisateur après les avoir appelées. Le tableau ci-dessous résume quelques-unes d’entre elles :
En effet, l’application web n’affiche que la sortie de la commande et lorsque celle-ci requiert une entrée utilisateur, la commande ne se termine pas et ne dispose donc pas de sortie à afficher :
En résumé, lors de l’exploitation d’une RCE, l’interpréteur de commandes appelé n’est pas interactif, car celui-ci est terminé une fois qu’il a exécuté la commande renseignée par l’utilisateur.
Ici, l’application tourne avec les droits www-data
et un attaquant souhaitera élever ses privilèges à root afin de compromettre le serveur hébergeant l’application. Il est plus compliqué d’élever ses privilèges avec les restrictions imposées par l’invité de commandes web actuel. C’est la raison pour laquelle un attaquant souhaitera souvent obtenir un interpréteur de commandes interactif via un terminal lors de l’exploitation d’une RCE.
Bind shell
Le concept
La première méthode afin d’obtenir un interpréteur de commandes interactif après l’exploitation d’une RCE est appelée Bind shell.
Celle-ci consiste à lier un interpréteur de commandes à un port spécifique en écoute sur l’hôte victime et de s’y connecter depuis la machine attaquante :
Au sein de cet article, nous utilisons le binaire nc (netcat). Celui-ci est un outil en ligne de commandes généralement présent de base sur les systèmes Unix, Windows, et macOS X.
Il permet de gérer les connexions réseau de la machine afin de notamment mettre en écoute un port. Il est également possible d’utiliser cet utilitaire afin de se connecter à un port distant ou local.
Après l’obtention de l’interpréteur de commandes, celui-ci ne sera pas encore totalement interactif, car il ne s’exécute pas au sein d’un pseudoterminal. Cependant, les commandes renseignées seront exécutées au sein du même interpréteur de commandes contrairement à celles entrées via la RCE. Ainsi, il devient possible d’obtenir un interpréteur de commandes interactif avec la commande python suivante :
python -c ‘import pty;pty.spawn(«/bin/bash»)’
Elle permet d’obtenir un nouvel interpréteur de commandes interactif bash en l’exécutant cette fois-ci au sein d’un pseudoterminal émulé (pty) identifié par un fichier tty.
La présence d’un pseudoterminal est donc nécessaire à l’interactivité d’un interpréteur de commandes, car c’est lui qui permet réellement d’interagir avec l’hôte.
En reprenant notre exemple, l’attaquant commence par mettre en écoute le port sur la machine victime via la RCE :
L’attaquant se connecte ensuite au port en écoute sur la machine victime et rend interactif l’interpréteur de commandes obtenu à l’aide de la commande Python citée précédemment :
Bien que nous soyons parvenus à obtenir ce Bind shell interactif relativement facilement, cette technique est peu utilisée par les attaquants pour différentes raisons :
1. La machine ciblée doit être exposée sur Internet ou sur le même réseau que l’attaquant (hors scénario de rebond) et aucune protection réseau ne doit être présente ;
2. Le port à exposer ne doit pas être utilisé par un service déjà lancé (ou potentiellement bloqué par le système) ;
3. Python (et le package pty) doivent être présents sur le système ;
4. Si on expose un bind shell sur Internet, d’autres attaquants pourraient s’y connecter…
Note : Dans l’exemple présenté, les machines attaquantes et victimes sont sur le même réseau et n’ont aucune restriction de flux leur permettant de se joindre simplement.
Les limitations…
De nos jours, lorsque l’on se rend sur un site web, il est rare que l’adresse IP associée au nom d’hôte contacté soit directement celle du serveur web auquel on accède. En effet, une architecture réseau moderne simplifiée s’apparente plutôt à l’exemple suivant :
Ainsi, lorsqu’un utilisateur accède à un serveur web, c’est uniquement grâce à la table NAT configurée préalablement sur les routeurs.
Le tableau ci-dessous représente un exemple de comment fonctionne une table NAT pour une requête HTTPS :
Ainsi, le routeur sait à quelle machine il doit relayer le paquet lorsqu’il arrive grâce au port renseigné.
Afin d’obtenir un Bind shell, il est nécessaire d’utiliser un autre port que celui sur lequel tourne l’application vulnérable. Le routeur ne relaiera donc pas le paquet vers le serveur vulnérable en renseignant un autre port et celui-ci ne pourra donc pas être directement contacté par un attaquant.
Afin de contourner ce problème de NAT, une autre méthode existe appelée Reverse shell.
« Bien que nous soyons parvenus à obtenir
ce Bind shell interactif relativement facilement,
cette technique est peu utilisée par les attaquants
pour différentes raisons. »