Le « ../../ » qui sort du dossier
Les 14 leçons du cours principal ont couvert les grandes familles du Top 10 OWASP 2025 : injections, XSS, mauvaise configuration, contrôle d'accès cassé... Ce Niveau 2 plonge dans des failles plus ciblées, moins connues mais tout aussi exploitées sur le terrain, chacune accompagnée de son propre labo d'attaque. On commence par l'une des plus faciles à rater et des plus dévastatrices : le path traversal.
Un site affiche des documents via une URL toute simple : monsite.com/voir?file=guide.txt. Derrière, le code lit le fichier demandé dans un dossier docs/ :
$fichier = $_GET['file'];
readfile("docs/" . $fichier); // lit le fichier demandé
Un visiteur ne demande pas guide.txt. Il demande ça :
monsite.com/voir?file=../../etc/passwd
Le serveur construit alors le chemin docs/../../etc/passwd. Et pour le système de fichiers, .. veut dire « le dossier parent ». En enchaînant des ../, on remonte hors du dossier docs/ et on lit n'importe quel fichier que le serveur a le droit de lire : /etc/passwd, un ../config.php avec les identifiants de la base, un ../.env bourré de secrets.
Ça, c'est le path traversal (ou directory traversal) : sortir du dossier prévu via le chemin. Quand le fichier est ensuite inclus et exécuté, on parle d'inclusion de fichier (LFI), qui peut mener jusqu'à l'exécution de code. C'est lié à l'A01 de l'OWASP 2025 (Broken Access Control).
Pourquoi ça marche
La cause est la même que pour toutes les injections : vous avez collé une entrée utilisateur dans quelque chose qui l'interprète. Ici, c'est un chemin de fichier. Le code attend un simple nom comme guide.txt, mais rien n'empêche d'y glisser des ../ qui changent complètement la destination.
L'attaquant a plusieurs variantes sous la main :
- Remonter avec
../autant de fois qu'il faut pour atteindre la racine, puis redescendre vers la cible. - Un chemin absolu directement :
/etc/passwd, si le code le permet. - Des encodages pour contourner un filtre naïf :
..%2f,..%252f,....//. - Lire le code source (avec
php://filteren PHP), pour trouver d'autres failles et des secrets.
Le faux remède : « je retire les ../ de l'entrée » ne suffit pas. Un ....// redevient ../ une fois les ../ retirés ; un ..%2f passe sous le filtre puis est décodé. La blacklist perd toujours. La vraie parade n'est pas de nettoyer le chemin, mais de ne jamais construire un chemin à partir d'une entrée brute (section 4).
À vous d'attaquer : lisez un fichier interdit
Voici un lecteur de documents vraiment vulnérable, simulé dans votre navigateur. Le serveur lit docs/<votre saisie>. Sortez du dossier docs/ pour lire un fichier secret du serveur. Puis activez la version sécurisée et constatez. Tout est simulé.
Bloqué ? Voir la solution
Le serveur colle votre saisie après docs/. Il suffit de remonter avec des ../ :
../config.php (les identifiants de la base), ../.env (les secrets), ou ../../etc/passwd (un fichier système).
Chaque ../ remonte d'un dossier. En version sécurisée, l'allowlist (ou basename + realpath) ne garde que le nom du fichier et vérifie qu'il reste dans docs/ : la remontée est bloquée.
Le correctif : ne jamais bâtir un chemin avec l'entrée brute
La meilleure parade est une allowlist : l'utilisateur ne choisit pas un chemin, il choisit une clé dans une liste que vous contrôlez. Son entrée ne touche jamais le système de fichiers.
$pages = ['guide' => 'guide.txt', 'cgu' => 'cgu.txt'];
$cle = $_GET['page'] ?? 'guide';
if (!isset($pages[$cle])) { http_response_code(404); exit; }
readfile(__DIR__ . '/docs/' . $pages[$cle]); // seul un fichier connu est servi
Si vous devez vraiment accepter un nom de fichier, deux gardes-fous se combinent. basename() retire tout chemin (les ../ disparaissent), et realpath() calcule le chemin réel pour vérifier qu'il reste dans le dossier autorisé :
$base = realpath(__DIR__ . '/docs');
$path = realpath($base . '/' . basename($_GET['file']));
// le fichier doit exister ET être sous $base
if ($path === false || strncmp($path, $base, strlen($base)) !== 0) {
http_response_code(403);
exit;
}
readfile($path);
Un include dynamique est encore pire. Mettre une entrée utilisateur dans include/require transforme une simple lecture de fichier en exécution de code : c'est la LFI, qui finit souvent en prise complète du serveur (via les logs, les sessions, ou un wrapper PHP). On ne construit donc jamais un nom de fichier à inclure à partir de la requête.
Défense en profondeur :
- une allowlist de fichiers ou d'identifiants (l'entrée ne touche pas le disque) ;
- sinon,
basename()+realpath()avec vérification du dossier de base ; - jamais d'
include/requireà partir de l'entrée utilisateur ; - droits fichiers minimaux : le process ne doit lire que ce qui le concerne (leçon 5) ;
- secrets hors du dossier web (
.envnon servi par le serveur).
Référence : OWASP Path Traversal.
La méthode et l'arsenal du pentester
Vous savez maintenant pourquoi la défense échoue quand elle repose sur un filtre ; voyons comment un attaquant construit exactement cette chaîne d'exploitation, outil par outil.
1. Repérer les paramètres « fichier ». Tout ce qui ressemble à ?file=, ?page=, ?doc=, ?lang=, ?template= : un paramètre qui finit dans un chemin ou un include. C'est là qu'on teste.
2. Sonder la remontée. On envoie ../, puis ../../, jusqu'à atteindre un fichier connu comme /etc/passwd (Linux) ou C:\Windows\win.ini (Windows). S'il s'affiche, la faille est confirmée.
3. Contourner et exploiter. Si un filtre bloque ../, on essaie les encodages (..%2f, ....//). En PHP, php://filter/convert.base64-encode/resource=... lit le code source. Et si l'entrée arrive dans un include, on vise la LFI jusqu'au code (empoisonnement de logs, fichiers de session).
L'arsenal.
- Burp Suite : fuzzer le paramètre avec Intruder et une liste de payloads de traversal.
- ffuf / DotDotPwn : automatiser les variantes de
../et d'encodage. - PayloadsAllTheThings (Directory Traversal) : la liste de référence des payloads et contournements.
L'impact
Lire un fichier arbitraire paraît mineur ; en pratique, c'est une mine. Le code source d'abord : le lire révèle d'autres failles et, souvent, des secrets en dur. Les fichiers de config ensuite (config.php, .env) : identifiants de base, clés d'API, secret de signature des sessions. Les fichiers système : /etc/passwd pour énumérer les comptes, les clés SSH (~/.ssh/id_rsa) pour se connecter directement.
Et quand la lecture devient une inclusion (LFI), elle peut se transformer en exécution de code à distance : on fait écrire son code dans un fichier que le serveur va inclure (un log, un fichier de session, un upload), et le tour est joué. Une « simple » lecture de fichier finit en prise complète du serveur.
Ce que ça révèle côté défense : le chemin d'un fichier ne se construit jamais avec une entrée utilisateur. On empile : allowlist (la base), basename + realpath dans le dossier de base, jamais d'include dynamique, et moindre privilège pour que le process ne puisse de toute façon pas lire grand-chose. La confiance se vérifie (leçon 1).
La checklist path traversal
À vérifier sur tout paramètre qui désigne un fichier.
- Allowlist de fichiers ou d'identifiants : l'entrée ne touche jamais le disque directement.
basename+realpathavec vérification du dossier de base, si un nom libre est inévitable.- Jamais d'
include/requirebâti sur l'entrée utilisateur. - Secrets hors du dossier web (
.env, configs non servies). - Moindre privilège sur les fichiers (le process lit le strict nécessaire).
Les références. L'OWASP Path Traversal détaille l'attaque et les contournements. Pour s'entraîner : les modules Path traversal et File inclusion de la Web Security Academy.
Rappel. Lire les fichiers d'un serveur qui n'est pas le vôtre, même « pour voir », est un accès non autorisé. On ne teste que ses propres systèmes ou une cible explicitement autorisée (leçon 1 du cours principal).
The "../../" that escapes the folder
The 14 lessons of the main course covered the major families of the OWASP Top 10 2025: injections, XSS, misconfiguration, broken access control... Level 2 dives into more targeted vulnerabilities — less well-known but just as actively exploited in the wild, each with its own attack lab. We start with one of the easiest to miss and most devastating: path traversal.
A site displays documents through a very simple URL: mysite.com/view?file=guide.txt. Behind it, the code reads the requested file in a docs/ folder:
$file = $_GET['file'];
readfile("docs/" . $file); // reads the requested file
A visitor doesn't ask for guide.txt. They ask for this:
mysite.com/view?file=../../etc/passwd
The server then builds the path docs/../../etc/passwd. And to the filesystem, .. means "the parent folder". By chaining ../, you climb out of the docs/ folder and read any file the server is allowed to read: /etc/passwd, a ../config.php with the database credentials, a ../.env full of secrets.
That's path traversal (or directory traversal): escaping the intended folder through the path. When the file is then included and executed, it's called file inclusion (LFI), which can lead all the way to code execution. It maps to A01 in OWASP 2025 (Broken Access Control).
Why it works
The cause is the same as for every injection: you glued user input into something that interprets it. Here, it's a file path. The code expects a plain name like guide.txt, but nothing stops you from slipping in ../ sequences that completely change the destination.
The attacker has several variants at hand:
- Climb up with
../as many times as needed to reach the root, then go back down to the target. - An absolute path directly:
/etc/passwd, if the code allows it. - Encodings to bypass a naive filter:
..%2f,..%252f,....//. - Read the source code (with
php://filterin PHP), to find other flaws and secrets.
Beware the false cure: "I strip the ../ from the input" isn't enough. A ....// becomes ../ again once the ../ are stripped; a ..%2f slips under the filter then gets decoded. The blacklist always loses. The real fix isn't cleaning the path, it's to never build a path from raw input (section 4).
Your turn to attack: read a forbidden file
Here's a genuinely vulnerable document reader, simulated in your browser. The server reads docs/<your input>. Escape the docs/ folder to read a secret server file. Then switch on the secure version and see. Everything is simulated.
Stuck? Show the solution
The server pastes your input after docs/. Just climb up with ../:
../config.php (the database credentials), ../.env (the secrets), or ../../etc/passwd (a system file).
Each ../ climbs one folder up. In the secure version, the allowlist (or basename + realpath) keeps only the filename and checks it stays inside docs/: the climb is blocked.
The fix: never build a path from raw input
The best defense is an allowlist: the user doesn't pick a path, they pick a key from a list you control. Their input never touches the filesystem.
$pages = ['guide' => 'guide.txt', 'cgu' => 'cgu.txt'];
$key = $_GET['page'] ?? 'guide';
if (!isset($pages[$key])) { http_response_code(404); exit; }
readfile(__DIR__ . '/docs/' . $pages[$key]); // only a known file is served
If you really must accept a filename, two guardrails combine. basename() strips any path (the ../ disappear), and realpath() computes the real path to verify it stays inside the allowed folder:
$base = realpath(__DIR__ . '/docs');
$path = realpath($base . '/' . basename($_GET['file']));
// the file must exist AND be under $base
if ($path === false || strncmp($path, $base, strlen($base)) !== 0) {
http_response_code(403);
exit;
}
readfile($path);
A dynamic include is even worse. Putting user input into include/require turns a plain file read into code execution: that's LFI, which often becomes a full server takeover (via logs, sessions, or a PHP wrapper). So you never build an included filename from the request.
Defense in depth:
- an allowlist of files or identifiers (input never touches the disk);
- otherwise,
basename()+realpath()with a base folder check; - never
include/requirefrom user input; - minimal file permissions: the process should only read what concerns it (lesson 5);
- secrets outside the web folder (
.envnot served by the server).
Reference: OWASP Path Traversal.
The pentester's method and arsenal
Now that you know why a naive filter fails on the defense side, here is how an attacker builds exactly that exploitation chain, tool by tool.
1. Spot the "file" parameters. Anything that looks like ?file=, ?page=, ?doc=, ?lang=, ?template=: a parameter that ends up in a path or an include. That's where you test.
2. Probe the climb. You send ../, then ../../, until you reach a known file like /etc/passwd (Linux) or C:\Windows\win.ini (Windows). If it shows, the flaw is confirmed.
3. Bypass and exploit. If a filter blocks ../, you try encodings (..%2f, ....//). In PHP, php://filter/convert.base64-encode/resource=... reads the source code. And if the input lands in an include, you go for LFI up to code (log poisoning, session files).
The arsenal.
- Burp Suite: fuzz the parameter with Intruder and a list of traversal payloads.
- ffuf / DotDotPwn: automate the
../and encoding variants. - PayloadsAllTheThings (Directory Traversal): the reference list of payloads and bypasses.
The impact
Reading an arbitrary file sounds minor; in practice, it's a goldmine. The source code first: reading it reveals other flaws and, often, hardcoded secrets. The config files next (config.php, .env): database credentials, API keys, the session-signing secret. System files: /etc/passwd to enumerate accounts, SSH keys (~/.ssh/id_rsa) to connect directly.
And when the read becomes an inclusion (LFI), it can turn into remote code execution: you get your code written into a file the server will include (a log, a session file, an upload), and that's it. A "simple" file read ends in a full server takeover.
What this reveals for defense: a file's path is never built from user input. You stack: allowlist (the base), basename + realpath inside the base folder, never a dynamic include, and least privilege so the process can't read much anyway. Trust is verified (lesson 1).
The path traversal checklist
To check on every parameter that names a file.
- Allowlist of files or identifiers: input never touches the disk directly.
basename+realpathwith a base-folder check, if a free name is unavoidable.- Never
include/requirebuilt on user input. - Secrets outside the web folder (
.env, configs not served). - Least privilege on files (the process reads the strict minimum).
The references. OWASP Path Traversal details the attack and bypasses. To practice: the Path traversal and File inclusion modules of the Web Security Academy.
Reminder. Reading the files of a server that isn't yours, even "just to see", is unauthorized access. Only test your own systems or an explicitly authorized target (lesson 1 of the main course).
Un dev fait readfile("docs/" . $_GET['file']). Un autre passe par une allowlist ($pages['guide']). Avant de dérouler : lequel laisse ?file=../../etc/passwd lire le fichier système ?
Voir la réponse
Le premier. La saisie est collée dans le chemin, et les ../ remontent hors de docs/ jusqu'à /etc/passwd. Le second résiste : l'utilisateur ne choisit qu'une clé (guide, cgu) d'une liste fermée, sa saisie ne touche jamais le système de fichiers. Quoi qu'il tape, seul un fichier connu est servi. Règle : on ne construit pas un chemin avec l'entrée brute.
One dev does readfile("docs/" . $_GET['file']). Another goes through an allowlist ($pages['guide']). Before you expand: which one lets ?file=../../etc/passwd read the system file?
Show the answer
The first. The input is pasted into the path, and the ../ climb out of docs/ up to /etc/passwd. The second resists: the user only picks a key (guide, cgu) from a closed list, their input never touches the filesystem. Whatever they type, only a known file is served. Rule: you don't build a path from raw input.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
💬 Ré-explique sans regarder
Avec tes mots : pourquoi coller une entrée dans un chemin de fichier est dangereux (que fait ../ ?), et pourquoi une allowlist (ou basename + realpath) protège ?
docs/). Or .. désigne le dossier parent ; en enchaînant des ../, l'attaquant remonte hors du dossier prévu et lit n'importe quel fichier accessible (config, .env, /etc/passwd, code source). Filtrer les ../ ne suffit pas (encodages, ....//). La parade : ne pas laisser l'entrée toucher le système de fichiers. Une allowlist fait choisir une clé dans une liste fermée. Sinon, basename retire tout chemin et realpath vérifie que le résultat reste bien dans le dossier autorisé. Et jamais d'include dynamique, qui transformerait la lecture en exécution de code.🧠 Rappel libre
Sans remonter : explique ce que fait ../ dans un path traversal, pourquoi filtrer les ../ ne suffit pas, et les deux parades (allowlist, basename+realpath).
../ remonte d'un dossier ; enchaîné, il sort du dossier prévu pour lire un fichier arbitraire (config, .env, /etc/passwd, code source). Filtrer les ../ ne suffit pas : un ....// redevient ../ après filtrage, et un ..%2f passe puis est décodé (la blacklist perd). Parades : (1) une allowlist où l'utilisateur choisit une clé d'une liste fermée, son entrée ne touche pas le disque ; (2) basename (retire tout chemin) + realpath (vérifie que le chemin réel reste dans le dossier autorisé). Et jamais d'include dynamique.⚖️ Juge le code de l'IA
Tu signales le path traversal à l'IA. Elle répond : « Je règle ça en retirant les ../ de la saisie avant de lire le fichier : str_replace('../', '', $_GET['file']). Comme ça on ne peut plus remonter. » Tu acceptes, ou tu rejettes ?
str_replace('../', '', …) appliqué une seule fois transforme ....// en ../ (il retire le ../ du milieu et recolle les bords). Et il ne voit pas ..%2f (encodé), ni un chemin absolu /etc/passwd, ni les antislash Windows. Retirer un motif laisse toujours une variante passer. La bonne parade n'est pas de nettoyer le chemin, c'est de ne pas laisser l'entrée le construire : une allowlist (l'utilisateur choisit une clé), ou basename() pour retirer tout chemin plus realpath() pour vérifier qu'on reste dans le dossier autorisé. On valide une intention, on ne soustrait pas des caractères.readfile("docs/" . $_GET['file']). Que lit ?file=../../etc/passwd ?str_replace('../', '', $input) ne suffit pas ?include est-il pire qu'un simple readfile ?Vous savez lire un fichier hors du dossier prévu. La leçon suivante laisse l'attaquant en déposer un : l'upload de fichiers, et le webshell qui prend le serveur.
Leçon 2 : Upload de fichiers →