CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Overview
Path Traversal (Directory Traversal) occurs when an application builds file paths from user-supplied input without validating them, so an attacker can reach files outside the intended directory.
Relationship to Other CWEs
CWE-22 vs CWE-73 vs CWE-41:
- CWE-22 (this page) - path traversal sequences (
../,..\) that escape the intended directory. Findings that mention "directory traversal" or "dot-dot-slash" belong here.
- CWE-73 (External Control of File Name or Path) - external control over which file is named, whether that is traversal or choosing an arbitrary file within an allowed directory. MITRE records it as CanPrecede CWE-22 rather than as a parent: external control is what makes traversal reachable, not a category above it. A CWE-73 finding that involves traversal sequences needs this page's guidance as well.
- CWE-41 (Improper Resolution of Path Equivalence) - equivalent representations of the same path (
/pathvs/path/vs//path) that bypass validation. Less common in findings; relevant mainly when validation compares strings without canonicalizing first.
Remediation overlap: All three benefit from canonicalization and allowlisting. CWE-22 and CWE-73 share most remediation steps.
OWASP Classification
A01:2025 - Broken Access Control
Risk
High: An attacker reads files outside the intended directory, such as application source or credential files. Where the traversal reaches a write operation, such as a file upload, it can instead place attacker-controlled content where the server will execute it.
Remediation Steps
Core Principle: Never allow untrusted input to directly control filesystem paths; canonicalize and enforce containment within an allowlisted root.
Trace the Data Path
Follow the untrusted data from where it enters to the file operation:
- Source: where file path data enters - user input, external files, databases, network requests
- Path Construction: string concatenation with
/or\ - Sink: the file operation (
open(),File(),readFile(), etc.) - Missing Validation: no canonicalization or containment check between the two
Use Indirect References (Primary Defense)
Do not use untrusted data as a file path. Have the client send an identifier and map it to a path on the server:
- Keep the permitted files, and the real path of each, in a database or other allowlist
- A request for
report_id=123maps to/var/reports/2024/report_123.pdf
Why this works: Users cannot manipulate paths they never see or control.
Validate Against Allowlist
If direct file references are unavoidable:
- Keep an explicit list of permitted files or directories
- Match file identifiers or filenames exactly, not raw user-supplied paths
- Reject any input that is not on the list
- Do not allowlist with wildcards or regular expressions
Canonicalize and Validate Path Containment
Resolve paths to their canonical form and verify containment:
Canonicalization process:
- Resolve symlinks and relative path segments (
..,.) - Convert to absolute canonical path
- Normalize path separators
- Verify the resolved path is equal to the allowed base directory or is inside it using path-component-aware containment, not a raw string prefix
- Do this check before opening the file
Example validation:
base_dir = "/var/www/uploads"
user_path = canonicalize(join(base_dir, user_input))
if not is_within_directory(base_dir, user_path):
reject()
Use Secure Path Manipulation APIs
Let the language's own path APIs do the joining and normalization. They do not replace the containment check:
- Do not build file paths by string concatenation
- Use the path joining functions that normalize separators (
path.join(),Path.Combine()) - Reject paths containing
.., absolute paths, or null bytes
Test with Path Traversal Payloads
Verify your fixes with attack patterns:
../../../etc/passwd(Unix/Linux)..\..\..\..\windows\system32\config\sam(Windows)- URL encoding:
%2e%2e%2f,%2e%2e\ - Double encoding:
%252e%252e%252f - Absolute paths:
/etc/passwd,C:\windows\system.ini - Null byte injection:
allowed.txt%00../../etc/passwd - Mixed separators:
..././..././etc/passwd - Confirm that legitimate file access still works
Common Pitfalls
- Denylisting
..instead of canonicalizing: Rejecting input that contains the literal substring../or..\- Attackers bypass this with URL encoding (%2e%2e%2f), double encoding (%252e%252e%252f), Unicode variants, or by omitting..entirely and using an absolute path or a symlink. The check never resolves the path, so it only catches the most obvious payload. - String-prefix containment check without a separator boundary: Verifying containment with
resolvedPath.startsWith(baseDir)after canonicalizing - Without appending a trailing separator tobaseDirfirst (or using a path-component-aware comparison), a sibling directory such as/app/uploads-secretor/app/uploads_evilincorrectly "matches" a base of/app/uploads, because the strings share a prefix even though one path is not actually inside the other. - Validating the raw input, then re-deriving the path differently for the actual file operation: Checking that the user-supplied string is free of traversal sequences, then joining or resolving it a second time before opening the file - If the validation and the file-open use different variables, different normalization steps, or run at different points in the request lifecycle, the value that was checked is not guaranteed to be the value that gets opened.
- Stripping
..once instead of rejecting the input: Replacing occurrences of../with an empty string as a "sanitization" step - A single non-recursive pass turns crafted input such as....//into../, because the traversal token only appears once the replacement has run.
Language-Specific Guidance
Language pages with concrete APIs and framework patterns:
- C# - Path.Combine, Path.GetFullPath for secure file access
- Go - path/filepath, os with canonical path validation
- Java - File, Path, Files API with canonical path validation
- JavaScript/Node.JS - fs, path modules, Express static with path resolution
- PHP - realpath with separator-terminated containment, open_basedir, stream wrappers
- Python - pathlib resolve with is_relative_to, tarfile extraction filters, safe_join