Path Traversal (Directory Traversal): Detection, Exploitation, and Remediation

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
Path traversal is one of the oldest and most consistently exploitable web application vulnerabilities. The root cause is straightforward: an application accepts a filename or file path as user input, uses it to construct a filesystem path, and reads the resulting file without verifying the path stays within the intended directory. The ../ (dot-dot-slash) sequence instructs the operating system to move up one directory level, and an attacker who chains enough of these sequences can navigate from the application's web directory to any file on the server.
The vulnerability appears in any code path that opens, reads, serves, or processes files based on user input: file download endpoints, template loading, configuration file selection, image processing, log viewer utilities, and static file servers. This guide covers the detection patterns, encoding bypass techniques that evade naive filters, and the canonicalization approach that provides bypass-resistant protection.
Detection: finding path traversal in code review and testing
Detecting path traversal requires both a static code review pass to find file I/O operations that accept user input and a dynamic testing phase that confirms exploitability against running endpoints. Static review focuses on tracing data flow from request parameters to file system calls, identifying every location where user-controlled strings are passed to open(), readFile(), FileInputStream, or equivalent functions. Dynamic testing then confirms that traversal payloads actually navigate outside the intended directory and return file contents. Because many applications implement naive string-replacement filters that can be bypassed with encoding variants, dynamic testing with multiple payload types is essential even when a filter appears to be present.
Code review: file operation patterns with user input
Trace the data flow from user-controlled inputs to file operations. File serving endpoints are the highest-probability target: APIs that serve files by name (/api/download?file=report.pdf), static asset servers that construct filesystem paths from URL segments, and template loaders that accept template names from configuration or user input. For each file operation identified, trace the input backward: does the path construction include any value from request parameters, URL segments, cookie values, or database records that themselves derive from user input? If yes, and if path canonicalization with base directory validation is not performed, the endpoint is likely vulnerable.
Dynamic testing: payload sequence and encoding variants
Test each suspected endpoint with a sequence of path traversal payloads of increasing complexity. Start with literal: ../../../etc/passwd. If filtered, try URL-encoded: ..%2F..%2F..%2Fetc%2Fpasswd. Try double URL-encoded: ..%252F..%252F..%252Fetc%252Fpasswd. Try path separators: ..%5C..%5C..%5Cwindows%5Csystem32%5Cdrivers%5Cetc%5Chosts (Windows). Try null byte: ../../../etc/passwd%00.jpg (for endpoints that validate file extensions). Use Burp Suite's Intruder with the path traversal wordlist from SecLists (path-traversal.txt) to automate payload rotation. A response containing root:x:0:0 (Linux /etc/passwd format) or file contents confirms exploitation.
Automated scanning
Burp Suite Pro's active scanner detects path traversal automatically by sending payloads and analyzing responses for file system content patterns. Nuclei templates (file-read category) include path traversal probes for common framework endpoints. OWASP ZAP's active scan rules include path traversal detection. These tools are most effective when they have full application coverage (authenticated sessions, all endpoints in scope). For API endpoints that require specific request formats or authentication, configure the scanner with the correct authentication tokens and request templates before running automated scans.
Remediation: canonicalization and allowlists
The correct remediation for path traversal is to resolve the user-supplied path to its canonical absolute form using the operating system's own path resolution functions and then verify that the resolved path falls within the permitted base directory. This approach is fundamentally different from input sanitization, which attempts to strip or block dangerous sequences from the raw string before use. Sanitization fails because there are too many encoding variants and normalization inconsistencies across platforms. Canonicalization succeeds because the OS resolves all encoding, ../ sequences, and symlinks before the application performs its boundary check. This section covers the canonicalization pattern in Python and Node.js, plus the allowlist approach for applications with a finite, known set of servable files.
Python: os.path.realpath() and startswith() validation
Secure pattern in Python: import os; BASE_DIR = os.path.realpath('/var/www/files'); requested = os.path.realpath(os.path.join(BASE_DIR, user_input)); if not requested.startswith(BASE_DIR + os.sep): raise ValueError('Path traversal detected'). os.path.realpath() resolves symlinks and ../ sequences to their absolute canonical path. The startswith() check with os.sep appended (to prevent '/var/www/files-secret' matching '/var/www/files') verifies the resolved path is within BASE_DIR. This works regardless of what encoding or ../ sequences the attacker uses, because the OS resolves them before the Python comparison.
Node.js: path.resolve() and startsWith() validation
Secure Node.js pattern: const path = require('path'); const BASE_DIR = path.resolve('/var/www/files'); const requestedPath = path.resolve(BASE_DIR, userInput); if (!requestedPath.startsWith(BASE_DIR + path.sep)) { return res.status(403).send('Forbidden'); } fs.readFile(requestedPath, (err, data) => { ... }). path.resolve() normalizes ../ sequences without filesystem access (unlike realpath which requires the file to exist). For additional validation, use fs.realpath() for files that must exist, which also resolves symlinks that could bypass path.resolve() on some filesystem configurations.
Allowlist approach: validate file names against an enumerable set
When the set of valid files is finite and known at deploy time, use an allowlist: maintain a map of permitted file identifiers to their actual filesystem paths, and reject any file identifier not in the map. Example: const ALLOWED_FILES = {report_q1: '/var/reports/q1.pdf', report_q2: '/var/reports/q2.pdf'}; const filePath = ALLOWED_FILES[userInput]; if (!filePath) return res.status(404).send('Not found'); fs.createReadStream(filePath).pipe(res). This approach is immune to all path traversal bypass techniques because the user input is never used to construct a filesystem path; it is only used as a key lookup into the static map. Apply this where the file set is enumerable and doesn't require dynamic path construction.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
The bottom line
Path traversal is fully preventable with a single pattern: resolve the user-supplied path to its canonical absolute form using the OS path resolution functions, then verify the canonical path starts with the permitted base directory before performing any file operation. Do not attempt to filter or sanitize the input string for ../ sequences or encoding variants, as these approaches are bypassed by encoding, normalization inconsistencies, and null bytes. Conduct a code audit for all file I/O operations that include user-controlled input, prioritize endpoints that serve files by name (download, view, export), and verify the fix with a path traversal fuzz test using the payloads in SecLists. The allowlist approach is even more robust for finite file sets and should be preferred where applicable.
Frequently asked questions
What is a path traversal vulnerability?
Path traversal occurs when an application uses user-controlled input to construct a filesystem path and accesses the resulting file without verifying the resolved path is within the intended directory. The attacker supplies ../ sequences (or their encoded equivalents) to navigate up the directory tree. For example, if an application serves files as /var/www/files/{filename} and the attacker supplies filename=../../../etc/passwd, the resolved path becomes /etc/passwd. If the application reads and returns that file, the attacker has read an arbitrary server file.
What files are most valuable to read via path traversal?
Linux/Unix targets: /etc/passwd (user accounts and home directories), /etc/shadow (password hashes, if running as root), ~/.ssh/id_rsa (SSH private keys), /etc/hosts (network configuration), application config files containing database credentials and API keys (often found via /proc/self/cmdline which reveals the process working directory and config file paths). Windows targets: C:\Windows\System32\drivers\etc\hosts, C:\inetpub\wwwroot\web.config (IIS credentials and connection strings), C:\Windows\repair\SAM (password database). Cloud-hosted applications: /proc/self/environ (environment variables including cloud credentials injected as env vars).
How do I detect path traversal in code?
Search for file operations that use user-controlled input without path validation: in Java grep for FileInputStream, FileReader, Files.readAllBytes, new File() with variables derived from request parameters; in Python grep for open(), pathlib.Path(), and os.path.join() calls where arguments include request.args, request.form, or other external input; in PHP grep for file_get_contents, readfile, fopen, include, and require with user input; in Node.js grep for fs.readFile, fs.createReadStream, path.join() where input derives from req.query, req.params, or req.body. Each match requires tracing whether the path argument is user-controlled and whether a path validation check precedes the file operation.
What are common path traversal filter bypass techniques?
URL encoding: ../ encoded as %2e%2e%2f or %2e%2e/ bypasses filters that check for literal '../'. Double URL encoding: %252e%252e%252f (encoding the percent sign) bypasses filters that decode once before checking. Path normalization inconsistency: some filters check for '../' but not for '..\' (Windows path separator), '..../' (doubled dots), or '.%2f' (encoded slash with literal dots). Null byte injection: ../../../etc/passwd%00.jpg tricks filters that check for allowed extensions (the application language reads the path up to the null byte, returning /etc/passwd). Case variation on case-insensitive filesystems: ..%5C (URL-encoded backslash) on Windows. Strip-and-replace bypass: if the filter removes '../' by replacement, '../../../' becomes '../../' after one replacement pass.
How do I fix path traversal in Java?
Use Path.toRealPath() to canonicalize the path and then verify it starts with the permitted base directory. Example: Path basePath = Paths.get('/var/www/files').toRealPath(); Path requestedPath = basePath.resolve(userInput).normalize(); if (!requestedPath.startsWith(basePath)) { throw new SecurityException('Path traversal attempt'); } This approach resolves ../ sequences and symbolic links to their actual filesystem paths before comparison. The check fails if the resolved path is outside basePath regardless of what encoding or bypass the attacker used, because the OS does the path resolution, not a string filter.
Is input sanitization enough to prevent path traversal?
Sanitization approaches that strip or block '../', '../..', and similar sequences are insufficient and can be bypassed through encoding, double encoding, and other evasion techniques documented above. The robust fix is not sanitization of the input string but validation of the resolved path after the OS processes it. Canonicalize the path using the platform's native path resolution (Path.toRealPath() in Java, os.path.realpath() in Python, realpath() in PHP, path.resolve() in Node.js), then compare the result to the permitted base directory. This approach is bypass-resistant because encoding is resolved by the OS before the comparison.
How does path traversal relate to local file inclusion (LFI)?
Path traversal and Local File Inclusion (LFI) are related but distinct. Path traversal reads file contents using a file I/O operation (reading the file as a byte stream or string). LFI is specific to PHP include/require operations: the application includes a user-controlled file as PHP code, potentially allowing code execution if the attacker can write to a file on the server (via file upload or log poisoning). Path traversal enables file disclosure; LFI enables code execution in PHP applications. Both share the same root cause (user-controlled input in file path construction) and are prevented by the same canonicalization approach.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
