File path injection — also called path traversal or directory traversal — is a vulnerability class (CWE-22) where an attacker manipulates a file path parameter to access files and directories outside the intended scope. In severe cases, it allows reading sensitive system files, overwriting configuration files, or achieving remote code execution.
File path injection consistently appears in OWASP Top 10 lists under A01:2021 – Broken Access Control and is one of the most common findings in enterprise code audits. This guide explains exactly how the vulnerability works, what it looks like in code, and how to fix it.
What Is File Path Injection?
File path injection occurs when user-controlled input is used to construct a file system path — and the application fails to validate or restrict that input before accessing the file.
The classic attack uses the ../ sequence (dot-dot-slash) to traverse up the directory tree:
https://example.com/download?file=../../etc/passwd
If the application naively appends this parameter to a base path and reads the resulting file, the attacker gets the contents of /etc/passwd — and by extension, can probe the entire file system.
How File Path Injection Works
Basic Path Traversal
Consider this vulnerable Python code:
import os
BASE_DIR = "/var/app/uploads/"
def download_file(filename):
path = BASE_DIR + filename
with open(path, "rb") as f:
return f.read()
If a user supplies filename = "../../etc/shadow", the resolved path becomes /var/app/uploads/../../etc/shadow → /etc/shadow. The attacker can read the system’s password hashes.
Encoded and Obfuscated Traversal
Attackers encode the traversal sequence to bypass naive string-matching defenses:
| Encoding | Payload |
|---|---|
| URL encoding | %2e%2e%2f = ../ |
| Double URL encoding | %252e%252e%252f |
| Unicode / UTF-8 | %c0%ae%c0%ae/ |
| Mixed separator | ..\ (Windows) |
| Null byte | ../../../etc/passwd%00.jpg |
A robust fix cannot rely on blocking ../ — it must validate the resolved canonical path.
File Inclusion via Path Injection
In PHP applications, path injection can escalate to Local File Inclusion (LFI), where the attacker forces the application to execute an arbitrary file as PHP code:
// Vulnerable
$page = $_GET['page'];
include("/var/www/html/pages/" . $page . ".php");
Payload: page=../../var/log/apache2/access.log%00 — if the attacker can inject PHP code into the access log (via a crafted User-Agent), this becomes Remote Code Execution.
What an Attacker Can Achieve
Depending on the vulnerability and system configuration, file path injection enables:
- Sensitive file read —
/etc/passwd,/etc/shadow, SSH private keys, application config files with database credentials,.envfiles - Source code disclosure — Reading application source files to find additional vulnerabilities
- Log poisoning → RCE — Writing attacker-controlled content into log files and then including them
- Config file overwrite — If the application writes to a user-supplied path, attackers can overwrite cron jobs, authorized_keys, web server configs
- Web shell upload — Writing a PHP/ASP/JSP file to a web-accessible directory
Real-World Impact
File path injection vulnerabilities have contributed to significant breaches:
- Accellion FTA (2021) — Path traversal in file transfer software led to data theft at dozens of organizations including Morgan Stanley, Kroger, and the Reserve Bank of New Zealand.
- Pulse Secure VPN (CVE-2019-11510) — Unauthenticated path traversal allowed reading arbitrary files including VPN session files and plaintext credentials.
- Apache HTTP Server (CVE-2021-41773) — Path traversal in the URL normalization layer allowed remote file read and code execution on Apache 2.4.49.
How to Detect File Path Injection in Code
Manual Code Review
Look for these patterns in code review:
- File I/O functions that accept user-controlled input:
open(),fopen(),FileStream(),include(),require() - String concatenation used to build file paths from user input
- Lack of path canonicalization before file access
- Missing allowlist of permitted filenames or directories
Static Code Analysis (SAST)
A static code analysis tool with taint analysis will automatically trace user-controlled data from HTTP request parameters (the source) through string concatenation operations to file system calls (the sink), flagging the injection wherever the path is not properly sanitized.
Offensive360 detects file path injection across all major languages — including interprocedural cases where the user input is passed through multiple function calls before reaching the file system operation. The findings include the exact file, line, data flow trace, and recommended fix.
How to Fix File Path Injection
Fix 1: Canonicalize and Validate the Resolved Path
The most reliable fix is to resolve the canonical absolute path of the requested file and verify it starts with the expected base directory:
Python:
import os
BASE_DIR = os.path.realpath("/var/app/uploads/")
def download_file(filename):
requested = os.path.realpath(os.path.join(BASE_DIR, filename))
if not requested.startswith(BASE_DIR + os.sep):
raise ValueError("Access denied: path traversal detected")
with open(requested, "rb") as f:
return f.read()
Java:
import java.nio.file.*;
Path baseDir = Path.of("/var/app/uploads/").toRealPath();
public byte[] downloadFile(String filename) throws IOException {
Path requested = baseDir.resolve(filename).normalize().toRealPath();
if (!requested.startsWith(baseDir)) {
throw new SecurityException("Path traversal detected");
}
return Files.readAllBytes(requested);
}
C#:
string baseDir = Path.GetFullPath("/var/app/uploads/");
public byte[] DownloadFile(string filename)
{
string requested = Path.GetFullPath(Path.Combine(baseDir, filename));
if (!requested.StartsWith(baseDir, StringComparison.OrdinalIgnoreCase))
throw new UnauthorizedAccessException("Path traversal detected");
return File.ReadAllBytes(requested);
}
Fix 2: Use an Allowlist of Permitted Files
If the set of accessible files is known and bounded, reject any filename not on the allowlist:
ALLOWED_FILES = {"report.pdf", "template.docx", "readme.txt"}
def download_file(filename):
if filename not in ALLOWED_FILES:
raise ValueError("File not permitted")
path = os.path.join("/var/app/uploads/", filename)
with open(path, "rb") as f:
return f.read()
This is the most secure approach when applicable — it completely eliminates the attack surface.
Fix 3: Use File IDs Instead of Names
Instead of accepting a filename from the user, accept an opaque identifier (UUID, numeric ID) and map it server-side to the actual file path:
FILE_MAP = {
"a3f1c2d4": "/var/app/uploads/report.pdf",
"b7e8f901": "/var/app/uploads/template.docx",
}
def download_file(file_id):
path = FILE_MAP.get(file_id)
if not path:
raise ValueError("Invalid file ID")
with open(path, "rb") as f:
return f.read()
There is no user-controlled path component at all — path traversal is architecturally impossible.
What NOT to Rely On
These defenses are insufficient on their own:
- Blocking
../— Easily bypassed with URL encoding, Unicode, or Windows separators - Stripping
../— Recursive stripping can be bypassed:....//becomes../after one strip - Extension validation — Null byte injection (
file.jpg%00) can bypass extension checks in some languages - Relative path checks — Without canonicalization, relative path manipulation still works
Checklist: Preventing File Path Injection
Use this checklist when reviewing code that handles user-supplied file paths:
- All file paths constructed from user input are canonicalized with
realpath()/toRealPath()/Path.GetFullPath()before use - The resolved path is validated to start with the expected base directory (with trailing separator)
- An allowlist of permitted filenames or file IDs is used where feasible
- User input is never passed directly to
include(),require(), or similar dynamic loading functions - Upload directories are outside the web root (or have execute permissions disabled)
- SAST scanning is part of the CI/CD pipeline to catch new instances automatically
Detecting File Path Injection with Offensive360
Offensive360 SAST performs deep taint analysis to detect file path injection across 60+ languages including Java, C#, Python, PHP, JavaScript/Node.js, Ruby, Go, and more. Findings include:
- The exact source (user-controlled input entry point)
- The complete data flow trace through your code
- The vulnerable sink (file system call)
- The severity and CWE classification (CWE-22)
- A recommended fix with code examples
Scans run against your source code — on-premise, in your CI/CD pipeline, or as a one-time assessment. Try a one-time code scan for $500 or request a demo.