Path Traversal (also known as Directory Traversal or Path Manipulation) is a security vulnerability that occurs when an application accepts user input to construct file paths without proper validation or sanitization.
The user input can potentially include malicious sequences that manipulate the file path to allow attacker unauthorized actions:
This allows the attacker to access the system's password file located at /etc/passwd, which is outside the intended directory structure.
Real-world Occurrences of Path Traversal
Zoom Path Traversal Vulnerability (2020)
In 2020, Zoom had a critical path traversal vulnerability in their messaging feature, that could allow attackers to execute arbitrary code on other meeting attendees' machines.
Impact: Potential remote code execution on users' systems.
Apache HTTP Server Path Traversal (2021)
A severe path traversal vulnerability in Apache HTTP Server 2.4.49 allowed attackers to map URLs to files outside the expected document root.
Impact: Remote attackers could access files outside of the web root directory, potentially exposing sensitive information and in some case remote code execution. This issue is known to be exploited in the wild.
Fixing Path Traversal
The most efficient way to fix a Path Traversal issue in your code is using proper input validation and path canonicalization.
Path canonicalization ensures that file paths are converted to their simplest form, removing any relative path components. Input validation ensures that user-provided paths only contain allowed characters and patterns, preventing directory traversal attempts.
Code Samples
Vulnerable Code
String userInput = request.getParameter("fileName");
File file = new File(baseDir + "/" + userInput);
FileInputStream fis = new FileInputStream(file);
Fixed Code
String userInput = request.getParameter("fileName");
File file = new File(baseDir, userInput).getCanonicalFile();
if (!file.getCanonicalPath().startsWith(new File(baseDir).getCanonicalPath())) {
throw new SecurityException("Invalid file path");
}
FileInputStream fis = new FileInputStream(file);
Fix Explanation
The vulnerable code directly concatenates user input into the file path.
The fix uses getCanonicalFile() to resolve the actual path.
Validates that the resolved path is within the intended base directory.
Throws a security exception if path traversal is attempted.
const safePath = path.normalize(userInput).replace(/^(\.\.(\/|\\|$))+/, '');
const filePath = path.join(baseDir, safePath);
if (!filePath.startsWith(baseDir)) {
throw new Error('Invalid file path');
}
fs.readFileSync(filePath);
Fix Explanation
The vulnerable code directly joins user input with the base directory.
The fix normalizes the path and removes any directory traversal sequences.
Validates that the final path stays within the base directory.
Throws an error if path traversal is attempted.
Vulnerable Code
file_path = os.path.join(base_dir, user_input)
with open(file_path, 'r') as file:
content = file.read()
Fixed Code
safe_path = os.path.normpath(user_input)
if safe_path.startswith('..'):
raise ValueError("Invalid file path")
file_path = os.path.join(base_dir, safe_path)
if not os.path.commonpath([file_path, base_dir]) == base_dir:
raise ValueError("Invalid file path")
with open(file_path, 'r') as file:
content = file.read()
Fix Explanation
The vulnerable code directly joins paths without validation.
The fix normalizes the path and checks for traversal attempts.
Uses commonpath to ensure the path stays within base directory.
Raises an error if path traversal is detected.
string safePath = Path.GetFullPath(Path.Combine(baseDir, userInput));
if (!safePath.StartsWith(Path.GetFullPath(baseDir), StringComparison.OrdinalIgnoreCase))
{
throw new SecurityException("Invalid file path");
}
string content = File.ReadAllText(safePath);
Fix Explanation
The vulnerable code combines paths without validation.
The fix uses GetFullPath to resolve the actual path.
Validates that the path stays within the base directory.
Uses case-insensitive comparison for Windows compatibility.
The vulnerable code directly concatenates user input.
The fix uses basename to remove directory components.
Uses realpath to resolve the actual path.
Validates that the path stays within base directory.
The vulnerable code directly concatenates paths.
The fix uses filesystem::path for safe path handling.
Validates that the parent path matches the base directory.
Throws an error if path traversal is detected.