Skip to main content

Path Traversal

Path Traversal (Directory Traversal) allows attackers to access files outside the intended directory by manipulating file path inputs with sequences like ../. Learn how to detect and prevent path traversal across languages.

High CWE-22 A01:2021 Broken Access Control
Affects: C#JavaJavaScriptPHPPythonRubyGo

What is Path Traversal?

Path Traversal (CWE-22), also known as Directory Traversal, is a vulnerability that allows an attacker to read, write, or delete files outside the application’s intended directory. It occurs when an application uses user-supplied input to construct file paths without adequately validating or sanitizing the path components.

The classic technique uses ../ (dot-dot-slash) sequences to navigate up the directory tree:

GET /download?file=../../../etc/passwd

Path traversal can be exploited to:

  • Read sensitive files — Configuration files, source code, private keys, /etc/passwd, /etc/shadow
  • Read application secrets — Database credentials in config files, API keys in .env files
  • Write malicious files — Overwrite configuration, plant web shells, modify log files
  • Achieve remote code execution — By writing executable files to web-accessible directories or overwriting application code

Why it matters

Path traversal is ranked under OWASP A01:2021 Broken Access Control, the number-one risk category. Its impact is significant because:

  1. Direct access to sensitive data — Attackers can read database connection strings, private keys, and other secrets stored on the filesystem
  2. Common in file-serving applications — Any feature that serves files based on user input (document downloads, avatar uploads, report generation) is at risk
  3. Often leads to full compromise — Reading SSH keys, AWS credentials, or application configs frequently enables complete infrastructure takeover
  4. Encoding bypasses are common — Simple blocklist-based defenses are routinely bypassed using URL encoding (%2e%2e%2f), double encoding, null bytes, or Unicode normalization
  5. Zip slip attacks — Archive extraction operations that don’t validate member paths can write files to arbitrary locations

How exploitation works

Basic directory traversal

An application serves user-uploaded documents:

GET /api/documents?name=report.pdf

The server constructs the path as /var/app/uploads/report.pdf. An attacker modifies the request:

GET /api/documents?name=../../../etc/passwd

The server resolves /var/app/uploads/../../../etc/passwd to /etc/passwd and returns its contents.

Encoding bypasses

Attackers use various encodings to evade filters:

# URL encoding
GET /api/documents?name=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

# Double URL encoding
GET /api/documents?name=%252e%252e%252f%252e%252e%252fetc%252fpasswd

# Null byte injection (older runtimes)
GET /api/documents?name=../../../etc/passwd%00.pdf

# Backslash on Windows
GET /api/documents?name=..\..\..\..\windows\win.ini

# Mixed separators
GET /api/documents?name=..%5c..%5c..%5cwindows%5cwin.ini

Zip slip

When extracting archives, a malicious zip can contain entries with traversal paths:

../../../../tmp/evil.sh
../../../var/www/html/shell.php

If the application extracts these without validating member paths, files are written to arbitrary locations.

Vulnerable code examples

C# / ASP.NET

// VULNERABLE: User input directly used in file path
[HttpGet("download")]
public IActionResult Download(string filename)
{
    string filePath = Path.Combine("/var/app/uploads", filename);
    return PhysicalFile(filePath, "application/octet-stream");
}

Java / Spring

// VULNERABLE: No path validation
@GetMapping("/files")
public ResponseEntity<Resource> getFile(@RequestParam String name) {
    Path filePath = Paths.get("/var/app/uploads/" + name);
    Resource resource = new FileSystemResource(filePath.toFile());
    return ResponseEntity.ok().body(resource);
}

Python / Django

# VULNERABLE: User-controlled path with no validation
def download_file(request):
    filename = request.GET.get('file')
    filepath = os.path.join('/var/app/uploads', filename)
    with open(filepath, 'rb') as f:
        return HttpResponse(f.read(), content_type='application/octet-stream')

Node.js / Express

// VULNERABLE: path.join does NOT prevent traversal
app.get('/files', (req, res) => {
    const filename = req.query.name;
    const filePath = path.join(__dirname, 'uploads', filename);
    res.sendFile(filePath);
});

Go

// VULNERABLE: Direct concatenation with user input
func downloadHandler(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    filepath := "/var/app/uploads/" + filename
    http.ServeFile(w, r, filepath)
}

Secure code examples

C# — Canonicalize and validate

// SECURE: Resolve canonical path and verify it stays within the base directory
[HttpGet("download")]
public IActionResult Download(string filename)
{
    string basePath = Path.GetFullPath("/var/app/uploads");
    string filePath = Path.GetFullPath(Path.Combine(basePath, filename));

    // Ensure the resolved path starts with the base directory
    if (!filePath.StartsWith(basePath + Path.DirectorySeparatorChar))
    {
        return BadRequest("Invalid filename");
    }

    if (!System.IO.File.Exists(filePath))
        return NotFound();

    return PhysicalFile(filePath, "application/octet-stream");
}

Java — Canonical path validation

// SECURE: Canonical path comparison
@GetMapping("/files")
public ResponseEntity<Resource> getFile(@RequestParam String name) throws IOException {
    File baseDir = new File("/var/app/uploads").getCanonicalFile();
    File requestedFile = new File(baseDir, name).getCanonicalFile();

    // Verify the canonical path is within the base directory
    if (!requestedFile.toPath().startsWith(baseDir.toPath())) {
        return ResponseEntity.badRequest().build();
    }

    Resource resource = new FileSystemResource(requestedFile);
    return ResponseEntity.ok().body(resource);
}

Python — Resolve and check

# SECURE: Resolve symlinks and verify path prefix
from pathlib import Path

def download_file(request):
    filename = request.GET.get('file')
    base_dir = Path('/var/app/uploads').resolve()
    file_path = (base_dir / filename).resolve()

    # Ensure resolved path is within base directory
    if not str(file_path).startswith(str(base_dir) + '/'):
        return HttpResponseBadRequest("Invalid filename")

    if not file_path.is_file():
        return HttpResponseNotFound()

    with open(file_path, 'rb') as f:
        return HttpResponse(f.read(), content_type='application/octet-stream')

Node.js — Resolve and verify

// SECURE: Resolve absolute path and verify prefix
const path = require('path');
const fs = require('fs');

app.get('/files', (req, res) => {
    const filename = req.query.name;
    const baseDir = path.resolve(__dirname, 'uploads');
    const filePath = path.resolve(baseDir, filename);

    // Ensure the resolved path is within the uploads directory
    if (!filePath.startsWith(baseDir + path.sep)) {
        return res.status(400).send('Invalid filename');
    }

    if (!fs.existsSync(filePath)) {
        return res.status(404).send('Not found');
    }

    res.sendFile(filePath);
});

Go — Clean and verify

// SECURE: filepath.Clean + prefix check
func downloadHandler(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    baseDir := "/var/app/uploads"

    // Clean the path to remove ../ sequences, then verify prefix
    cleanPath := filepath.Join(baseDir, filepath.Clean("/"+filename))
    if !strings.HasPrefix(cleanPath, baseDir+string(filepath.Separator)) {
        http.Error(w, "Invalid filename", http.StatusBadRequest)
        return
    }

    http.ServeFile(w, r, cleanPath)
}

What Offensive360 detects

Our SAST engine traces user-controlled input to file system operations and identifies unsafe path construction. We detect:

  • Direct path concatenation — User input concatenated or interpolated into file paths without canonicalization
  • Ineffective sanitization — Simple string replacement of ../ (which can be bypassed with ....// or encoding tricks)
  • path.join / Path.Combine misuse — These functions combine paths but do not prevent traversal; we flag when they are used without subsequent canonical path validation
  • Zip/archive extraction — Archive member paths not validated against a base directory during extraction (Zip Slip)
  • File upload destination — User-controlled filenames used to determine upload storage paths
  • Symlink following — File operations that follow symbolic links without restricting the target to an allowed directory

Each finding provides the full data-flow trace and identifies the specific file operation (read, write, delete, include) that is reachable.

Remediation guidance

  1. Canonicalize then validate — Always resolve the final absolute path using Path.GetFullPath(), getCanonicalFile(), Path.resolve(), or equivalent, and then verify the result starts with your expected base directory. This is the most reliable defense.

  2. Never trust user input for filenames — If possible, use an indirect reference (e.g., a database ID that maps to a filename) instead of accepting filenames directly from users.

  3. Strip path separators — Remove or reject input containing /, \, .., null bytes, and URL-encoded variants. But do this as defense-in-depth, not as your sole protection.

  4. Use a chroot or sandboxed directory — Configure the application or container so that the process literally cannot access files outside its designated directory.

  5. Validate archive entries — When extracting zip/tar archives, check that each entry’s path resolves to a location within the target directory before writing.

  6. Disable directory listing — Ensure your web server does not expose directory indexes, which help attackers discover files to target.

False-positive considerations

Offensive360 may flag file operations that are safe in certain contexts:

  • Static/constant paths — If the file path is entirely hardcoded and no user input influences it, there is no traversal risk
  • Integer-based lookups — If user input is parsed to an integer and used to look up a filename from a database or map
  • Canonical path validation present — If the code resolves the path and checks the prefix, but the analysis doesn’t recognize the custom validation function
  • Containerized environments — Even if traversal occurs, a container’s filesystem may not contain sensitive files; however, this is not a reliable mitigation

References

Author: Offensive360 Security Research
Last reviewed: March 1, 2026

Related vulnerabilities

Detect Path Traversal in your code

Run Offensive360 SAST against your codebase to find this and hundreds of other vulnerabilities.