Path Traversal Testing: Directory Traversal, LFI, and File Inclusion Vulnerabilities
A web application at a financial services firm is serving a document preview feature. The URL looks like this: https://app.example.com/preview?file=reports/2026-q1.pdf. A security engineer changes the parameter to file=../../../../etc/passwd and receives the contents of the server's password file in the HTTP response. Two thousand lines of user accounts, immediately readable.
Path traversal is one of the oldest vulnerability classes in web security, documented for over two decades — and still found in production applications today. This guide covers how to test for it systematically, the encoding and bypass techniques that evade naive defenses, and what good remediation actually looks like.
What Path Traversal Actually Is
Path traversal (also called directory traversal or Local File Inclusion — LFI) occurs when an application uses user-controlled input to construct a file system path and fails to adequately restrict which files can be accessed. The ../ sequence moves up one directory level on Unix systems; ..\ does the same on Windows. Chaining multiple traversal sequences lets an attacker walk from the application's web root up to the filesystem root and then back down to any readable file.
The classic target is /etc/passwd on Linux — not because the file itself causes damage, but because reading it proves the vulnerability is exploitable and provides a list of usernames for further attacks. More impactful targets include:
- /etc/shadow — hashed passwords (requires root read access)
- /etc/hosts — internal network topology
- Application configuration files — database credentials, API keys, connection strings
- SSH private keys —
/home/user/.ssh/id_rsa - Web server configuration —
/etc/nginx/nginx.conf,/etc/apache2/sites-enabled/ - Application source code — when the app runs from a readable directory
- Log files — which can be leveraged for log poisoning to escalate to Remote Code Execution
Remote File Inclusion: The More Dangerous Cousin
Remote File Inclusion (RFI) occurs when the file inclusion mechanism can be pointed at an external URL. In PHP applications, if allow_url_include is enabled and the application passes user input directly to include() or require(), an attacker can point the parameter to a remote server they control and have the application execute arbitrary PHP code.
RFI has largely disappeared from modern applications because PHP now disables allow_url_include by default and modern frameworks don't use raw include statements driven by user input. But legacy PHP applications, CMSes running on old PHP versions, and shared hosting environments still have it. Always test for RFI when you find a path traversal, because the remediation effort is the same and the exploitability difference is significant.
Where to Look for Path Traversal
Path traversal vulnerabilities are most likely in parameters that reference files or resources by name:
- File download endpoints:
?file=,?path=,?doc=,?filename= - Template or theme selectors:
?template=,?theme=,?skin= - Language or locale files:
?lang=,?locale= - Log viewers, report generators, export tools
- Image resizers and media servers with filename parameters
- API endpoints that return file content based on identifiers
- Administrative interfaces that display server logs or config files
Don't limit testing to GET parameters. Path traversal can appear in POST body parameters, JSON payloads, HTTP headers (particularly X-File-Name or custom headers), cookie values, and multipart form field names.
Basic Detection: The Traversal Sequences
Start with the simplest payloads and work toward more complex ones only when the basics don't work. The initial test is just four traversal sequences followed by a known file:
../../../../etc/passwd
../../../etc/passwd
../../etc/passwd
../etc/passwd
If any of these produces the /etc/passwd file content in the response, the vulnerability is confirmed. On Windows targets, try:
..\..\..\..\windows\win.ini
../../../../windows/system32/drivers/etc/hosts
The number of traversal sequences needed depends on how deep in the directory tree the application's base path is. Start with four; if that doesn't work, try eight. If the application is clearly rejecting the sequences, move to bypass techniques.
Bypass Techniques: When Naive Defenses Are In Place
Many applications implement partial defenses — blocking ../ but not encoding variants, or stripping traversal sequences but not doing so recursively. Here are the bypass techniques to test systematically:
URL Encoding
%2e%2e%2f%2e%2e%2fetc%2fpasswd (../ encoded)
%2e%2e/%2e%2e/etc/passwd (../ partially encoded)
..%2Fetc%2Fpasswd (/ encoded, .. not)
Double URL Encoding
%252e%252e%252f%252e%252e%252fetc%252fpasswd
Useful when the server decodes once on input validation and again during processing. The first decode produces a %2e sequence that passes the filter; the second decode produces the . character.
Unicode/UTF-8 Encoding
..%c0%af../etc/passwd (/ as overlong UTF-8)
..%ef%bc%8f../etc/passwd (/ as fullwidth solidus)
Null Byte Injection
../../../../etc/passwd%00.jpg
In languages where file paths are passed to underlying C libraries, a null byte terminates the string. The validation code sees .jpg and passes; the filesystem sees the path up to the null byte. This affects old PHP versions and some Python/Ruby implementations. Modern languages handle null bytes correctly, but it's worth testing on legacy systems.
Stripping Bypasses
....//....//etc/passwd (double traversal — strips ../ once)
..././..././etc/passwd (strips ./ but not ../)
If the application strips ../ but doesn't do so recursively, ....// becomes ../ after one pass of stripping.
Absolute Path
/etc/passwd
/var/www/../../etc/passwd
Some applications just prepend a base directory to the input without checking for traversal sequences — which means an absolute path also works.
Using Burp Suite for Path Traversal Testing
Burp Suite's Intruder makes systematic traversal testing straightforward. Capture a request containing a file parameter, send it to Intruder, mark the parameter value as the injection point, and load a path traversal wordlist. The PortSwigger SecLists repository contains a comprehensive list at Fuzzing/LFI/LFI-Jhaddix.txt — over 900 payloads covering encoding variants, Windows and Linux paths, and null byte variations.
Configure Intruder to grep responses for known file content strings:
root:x:— Unix /etc/passwd[extensions]— Windows win.inilocalhost— /etc/hosts or Windows hosts file
Any response containing these strings is a confirmed find, regardless of HTTP status code. Some applications return 200 with the file content embedded in a larger response; Burp's grep highlighting will catch it.
Automated Detection with Nikto and Nuclei
Both tools have built-in path traversal tests. Nikto checks for common traversal sequences in file parameters and also tests for known vulnerable paths in specific application types. Nuclei's path-traversal template category covers hundreds of known CVEs and generic detection patterns:
# Nikto basic scan
nikto -h https://target.example.com -Tuning 3
# Nuclei path traversal templates
nuclei -u https://target.example.com -tags lfi,path-traversal
# ffuf with LFI wordlist
ffuf -u "https://target.example.com/download?file=FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-mr "root:x:" -mc all
The -mr flag in ffuf matches on response body content — here, looking for the passwd file indicator. Combine with -mc all to see all status codes rather than just 200s.
From LFI to Remote Code Execution
LFI alone is usually a high-severity finding. But in PHP applications, LFI can escalate to full Remote Code Execution through several techniques:
Log Poisoning
PHP applications that include log files via LFI can be escalated by first injecting PHP code into a log file, then including that log file:
- Send a request with a PHP payload in the User-Agent header:
User-Agent: <?php system($_GET['cmd']); ?> - This gets written to the web server access log
- Include the log file via LFI:
?file=../../../../var/log/apache2/access.log&cmd=id - The PHP engine executes the injected code
PHP Session Files
If an application stores PHP session data in a predictable location (/tmp/sess_[sessionid]) and the session data contains user-controlled values, injecting PHP code into session data and then including the session file achieves code execution.
PHP Wrappers
PHP's stream wrappers can be used when the include path accepts them:
# Read PHP source code without execution
?file=php://filter/convert.base64-encode/resource=index.php
# Execute arbitrary PHP (requires allow_url_include on)
?file=php://input (with POST body: <?php system('id'); ?>)
The php://filter wrapper is particularly useful because it doesn't require allow_url_include — it just base64-encodes the requested file's content, letting you read PHP source code rather than having it execute.
Remediation: What Actually Works
The correct fix is to not construct file paths from user input at all. Use a lookup map: user provides an identifier, server maps it to a file path internally, user never influences the path directly.
When that's not feasible (legacy code, legitimate file browsing features), the defense-in-depth approach:
- Resolve the canonical path before validation — use
realpath()in PHP,Path.toRealPath()in Java,os.path.realpath()in Python. This resolves all traversal sequences and symlinks before you check whether the path is allowed. - Check that the resolved path starts with the allowed base directory —
if (!realpath.startsWith(allowedBase)) throw new SecurityException() - Use a whitelist of allowed files or extensions — if the feature only needs to serve PDFs, only allow
.pdfextensions - Run the web server process with minimal filesystem permissions — even if traversal succeeds, the process shouldn't be able to read
/etc/shadow - Never pass user input to include/require in PHP — if you do, ensure
allow_url_include = Offinphp.ini
| Defense | What it stops | What it doesn't stop |
|---|---|---|
Strip ../ sequences |
Basic traversal | Encoding variants, double-stripping bypasses |
| URL decode before validation | Single-encoded payloads | Double-encoded payloads, Unicode variants |
| realpath() + prefix check | All traversal variants, symlinks | Nothing — this is the correct fix |
| File whitelist | Access to unlisted files | Traversal within listed files (minimal risk) |
| Minimal process permissions | Access to sensitive system files | Application config files in web root |
What to Document in Your Finding
A well-written path traversal finding includes:
- The vulnerable parameter and full HTTP request that demonstrates the issue
- The file that was accessed (usually
/etc/passwdas the proof-of-concept) - The number of traversal sequences required (indicates how deep the web root is)
- Whether any bypass techniques were needed
- Whether the vulnerability is exploitable for RCE via log poisoning or PHP wrappers
- What sensitive files are accessible from the server context (enumerate: config files, .env files, SSH keys)
- CVSS score — typically 7.5 High for read-only LFI, 9.8 Critical if RCE is achievable
Ironimo scans your web application for path traversal and local file inclusion vulnerabilities using the same detection techniques pentesters use — including encoding variants, null byte injection, and wrapper-based bypasses. Every confirmed finding comes with the full HTTP request and response, so your developers can reproduce it immediately without needing a security specialist to walk them through it.
Start free scan