Path Traversal: How ../ Reads Files You Never Meant to Share
Path traversal lets an attacker climb out of an intended directory with dot-dot-slash sequences and read arbitrary files on the server. Here is how it works, the encodings that defeat naive filters, and the canonicalization fix that stops it.
../../../etc/passwdOn this page
Path traversal — also called directory traversal — happens when an application builds a filesystem path out of user input and never checks that the result stays where it is supposed to. By injecting ../ sequences, an attacker walks up and out of the intended folder and reaches files anywhere the application can read: /etc/passwd, configuration secrets, application source, even credentials.
It is one of the oldest bugs on the web and still one of the most common, because the vulnerable pattern looks so reasonable: take a filename, open it, return it. This post shows how that single missing check turns into arbitrary file disclosure, and how to close it for good.
How it happens
The root cause is concatenating user input straight into a path. A download endpoint that serves files from a fixed folder is the classic example:
$file = $_GET['file']; $path = "/var/www/uploads/" . $file; readfile($path);
For a normal request like ?file=invoice.pdf, the path resolves to /var/www/uploads/invoice.pdf and everything works. The developer assumes file is just a name. But it is data the attacker controls, and / and . are perfectly valid characters in it — so the input can describe a journey out of the uploads directory.
A concrete attack
Instead of a filename, the attacker sends a chain of parent-directory hops:
GET /download?file=../../../etc/passwd HTTP/1.1
Host: app.example
The path the application opens becomes:
/var/www/uploads/../../../etc/passwd -> /etc/passwd
Each ../ cancels one directory level. Three of them climb from uploads past www and var to the filesystem root, and the server happily returns the contents of /etc/passwd. When a filter naively strips the literal string ../, attackers reach for encoded and malformed variants that decode back to the same sequence later in the pipeline:
..%2f..%2f..%2fetc/passwd # URL-encoded slash
..%252f..%252f..%252fetc/passwd # double-encoded
....//....//....//etc/passwd # survives a single "../" -> "" strip
..%c0%af..%c0%afetc/passwd # overlong UTF-8 slash
..%00/etc/passwd # null-byte truncation of an appended suffix
The application thought it was choosing the file. The attacker only had to phrase the filename as directions, and the filesystem followed them right out of the sandbox.
What an attacker gains
Arbitrary read access to the server's filesystem is a deep foothold:
- Credential and config theft —
.envfiles, database passwords, API keys and private keys. - Source code disclosure — reading the application's own code to find more vulnerabilities.
- System reconnaissance —
/etc/passwd,/proc/self/environand process data that map out the host. - Escalation to code execution — when the same flaw allows writing files, an attacker can drop a web shell or poison a config.
The fix: canonicalize, then verify the path stays inside
The dependable fix is not to filter bad strings — it is to resolve the final path and confirm it still lives under your base directory. Canonicalize first (so every ../, symlink and encoding is collapsed to one real path), then compare:
$base = realpath("/var/www/uploads"); $requested = realpath($base . "/" . $_GET['file']); if ($requested === false || !str_starts_with($requested, $base . DIRECTORY_SEPARATOR)) { http_response_code(404); exit; } readfile($requested);
realpath() resolves the path to its true location on disk; the str_starts_with check rejects anything that escaped the base folder. Layer extra defences on top: where you can, avoid user-controlled paths entirely and map an opaque ID to a known filename in a lookup table; validate filenames against a strict allowlist (^[a-zA-Z0-9._-]+$, no slashes); and run the service with least privilege so even a successful traversal reads as little as possible.
How SelfSec proves this one
Scanner behaviour- Module
- Path Traversal
- How it probes
- Traversal, encoding and truncation variants against every file-referencing parameter, including archive and upload paths.
- How it confirms
- The response has to carry file content the baseline request did not return.
Reported as CWE-22 · ATT&CK T1083 · SARIF 2.1.0
A reflected response alone is not enough to promote a Path Traversal finding — the classical engine has to confirm the behaviour before it reaches the report. See the detection engine
SelfSec is intended strictly for authorized security testing of systems you own or are explicitly permitted to assess.
Do both things about Path Traversal
SelfSec covers this class from both sides — the scanner confirms it in your own app, the firewall blocks it in front of your origin while the fix ships.