Path Traversal Remediation

Risk Severity
๐ŸŸ  High
Fix Effort
๐Ÿ”ง Medium
Est. Time
โฑ๏ธ 2-4 hours
Reference
A01:2021 CWE-22

Path traversal (directory traversal) attacks allow attackers to access files outside the intended directory using sequences like "../". This can lead to reading sensitive files, source code, or even code execution.

Impact

Successful path traversal can expose configuration files, credentials, source code, and system files. Combined with file upload vulnerabilities, it can lead to remote code execution.

Understanding Path Traversal

Attack Examples

Basic traversal:
../../../etc/passwd ..\..\..\..\windows\win.ini
Encoded variants:
%2e%2e%2f%2e%2e%2f ....//....//etc/passwd

Remediation Strategies

1. Avoid User Input in Paths

Use database IDs or UUIDs that map to files instead of filenames.

2. Canonicalize and Validate

Resolve the full path, then verify it starts with the allowed base path.

3. Allowlist Approach

Only allow specific, predefined files or patterns.

4. Chroot/Sandbox

Run file operations in a restricted directory jail.

Python

python
import os
from pathlib import Path

UPLOAD_DIR = Path('/var/www/uploads').resolve()

def safe_file_access(user_filename):
    """Safely access a file within the upload directory."""
    # Construct the full path
    requested_path = (UPLOAD_DIR / user_filename).resolve()
    
    # Verify path is within allowed directory
    if not str(requested_path).startswith(str(UPLOAD_DIR)):
        raise ValueError("Access denied: path traversal attempt")
    
    # Additional check: file must exist
    if not requested_path.is_file():
        raise FileNotFoundError("File not found")
    
    return requested_path

# Alternative: Use indirect references
FILE_MAPPING = {
    'doc1': '/var/www/uploads/document1.pdf',
    'doc2': '/var/www/uploads/document2.pdf',
}

def get_file_by_id(file_id):
    if file_id not in FILE_MAPPING:
        raise ValueError("Invalid file ID")
    return FILE_MAPPING[file_id]

Java

java
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

public class SafeFileAccess {
    private static final String BASE_DIR = "/var/www/uploads";
    
    public static File safeGetFile(String userInput) throws IOException {
        // Resolve and canonicalize the path
        Path basePath = Paths.get(BASE_DIR).toRealPath();
        Path requestedPath = basePath.resolve(userInput).normalize().toRealPath();
        
        // Verify path is within base directory
        if (!requestedPath.startsWith(basePath)) {
            throw new SecurityException("Path traversal attempt detected");
        }
        
        return requestedPath.toFile();
    }
    
    // Alternative: Sanitize filename (remove path components)
    public static String sanitizeFilename(String filename) {
        return new File(filename).getName();  // Returns only the filename
    }
}

Node.js

javascript
const path = require('path');
const fs = require('fs');

const BASE_DIR = '/var/www/uploads';

function safeFileAccess(userFilename) {
    // Resolve to absolute path
    const requestedPath = path.resolve(BASE_DIR, userFilename);
    
    // Normalize and check if within base directory
    const normalizedBase = path.normalize(BASE_DIR);
    const normalizedPath = path.normalize(requestedPath);
    
    if (!normalizedPath.startsWith(normalizedBase + path.sep)) {
        throw new Error('Access denied: path traversal attempt');
    }
    
    // Additional validation
    if (!fs.existsSync(requestedPath)) {
        throw new Error('File not found');
    }
    
    return requestedPath;
}

// Alternative: Use basename to strip directory components
function sanitizeFilename(filename) {
    return path.basename(filename);
}

PHP

php
<?php
define('BASE_DIR', '/var/www/uploads');

function safeFileAccess($userFilename) {
    // Build full path and resolve it
    $requestedPath = realpath(BASE_DIR . DIRECTORY_SEPARATOR . $userFilename);
    $basePath = realpath(BASE_DIR);
    
    // realpath returns false if file doesn't exist
    if ($requestedPath === false) {
        throw new Exception('File not found');
    }
    
    // Verify path is within base directory
    if (strpos($requestedPath, $basePath) !== 0) {
        throw new Exception('Access denied: path traversal attempt');
    }
    
    return $requestedPath;
}

// Alternative: Strip directory from filename
function sanitizeFilename($filename) {
    return basename($filename);
}
?>

๐Ÿงช Testing Verification

Path Traversal Test Payloads

text
# Basic traversal - should be blocked
../../../etc/passwd
..\..\..\windows\win.ini

# URL encoded
%2e%2e%2f%2e%2e%2fetc%2fpasswd
%2e%2e%5c%2e%2e%5cwindows%5cwin.ini

# Double encoding
%252e%252e%252f

# Null byte (older systems)
../../../etc/passwd%00.jpg

# Unicode/overlong encoding
..%c0%af..%c0%af

# Path normalization bypass
....//....//etc/passwd
..;/..;/etc/passwd

# Expected: All should fail or return safe response

Common Mistakes

Blocklist Approach

python
# DON'T: Easy to bypass
if "../" in filename:
    reject()

Bypassed with encoding or ....//

Correct Approach

python
# DO: Resolve and verify
real_path = Path(base / file).resolve()
if not str(real_path).startswith(base):
    reject()

Canonicalization handles all bypasses

Checking Before Canonicalization

python
# DON'T: Check raw input
if not raw_path.startswith(base):
    reject()
# Then use raw_path...

Path may resolve differently

Correct Approach

python
# DO: Canonicalize first
real = os.path.realpath(raw_path)
if not real.startswith(base):
    reject()

Check the actual resolved path

Path Traversal Prevention Checklist

  • โ˜ User input not used directly in file paths
  • โ˜ Path canonicalization before access checks
  • โ˜ Resolved path verified to be within base directory
  • โ˜ Filename sanitization (strip directory components)
  • โ˜ Indirect file references used where possible
  • โ˜ File system permissions properly configured