Skip to content

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Schwachstellenklassen Glossary

Path Traversal - Verzeichnisüberschreitung (Directory Traversal)

Path traversal (CWE-22, OWASP A01:2021) allows attackers to access files outside the permitted directory using ../ sequences. Objective: Reading sensitive files (/etc/passwd, web.config, .env, SSH keys); in the worst-case scenario, writing or executing files. Variants: URL-encoded traversal (%2e%2e%2f), double-encoded (%252e%252e%252f), null-byte injection (.php%00), Windows paths with backslash notation. Protection: absolute path validation with realpath(), allowlist-based file selection.

Path Traversal is one of the oldest and most persistent web vulnerabilities. Despite its long history, it regularly turns up in penetration tests—and in 2021, CVE-2021-41773 (Apache HTTP Server) made global headlines: a single %2e%2e/cgi-bin/ was enough to trigger remote code execution. The pattern is always the same: unvalidated user input flows directly into file system access.

Basic Principle

Normal file access:
  https://example.com/download?file=report.pdf
  → Server: /var/www/uploads/report.pdf ← allowed

Path traversal attack:
  https://example.com/download?file=../../../etc/passwd
  → Server: /var/www/uploads/../../../etc/passwd
           = /etc/passwd  ← outside the allowed directory!

Simple vulnerable implementation (PHP):
  $file = $_GET['file'];
  readfile('/var/www/uploads/' . $file);  // ← NO protection!

Typical attack targets:
  Linux:
  → /etc/passwd (user list)
  → /etc/shadow (password hashes, if as root)
  → /etc/hosts (network configuration)
  → ~/.ssh/id_rsa (SSH private key!)
  → /proc/self/environ (environment variables containing API keys!)
  → /proc/self/cmdline (process arguments)

  Windows:
  → C:\Windows\win.ini (system information)
  → C:\Windows\System32\drivers\etc\hosts
  → C:\inetpub\wwwroot\web.config (database passwords!)
  → C:\Users\[user]\AppData\Local\Microsoft\Outlook\*.ost

  Web applications:
  → .env (database passwords, API keys, secrets!)
  → config/database.yml (Rails)
  → WEB-INF/web.xml (Java)
  → ../../../wp-config.php (WordPress)

Bypass Techniques

Bypassing simple filters:

Filter 1: Replaces "../" with "" (non-recursive):
  Bypass: "....//....//....//etc/passwd"
  → After replacement: "../../../etc/passwd"!

Filter 2: URL decoding only once:
  Bypass: "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
  → Decoded: "../../../etc/passwd"

Filter 3: Slash is filtered:
  Bypass: "%252e%252e%252fetc%252fpasswd"  (double-encoded)
  → First decoding: "%2e%2e%2fetc%2fpasswd"
  → Second decoding: "../../../etc/passwd"

Filter 4: Expects .jpg at the end:
  Bypass (Null byte, Legacy PHP < 5.3.4):
  "../../../../etc/passwd%00.jpg"
  → PHP stops at null byte: reads /etc/passwd
  → .jpg check: filename ends with .jpg ← PASS!

Filter 5: Checks if path starts with "/uploads/":
  Bypass: "/uploads/../../../../etc/passwd"
  → Starts with /uploads/ → PASS!
  → But contains path traversal!

Windows-specific bypasses:
  Backslash: "..\..\..\..\Windows\win.ini"
  URL-encoded: "..\"..\"..\"..\Windows\win.ini"
  UNC paths: "\\server\share\file"
  Drive letter: "C:\Windows\win.ini" (if absolute paths are accepted)

ZIP/archive-based path traversal (Zip Slip):
  ZIP archive containing the entry "../../../evil.php"
  → When the application unpacks the ZIP: evil.php ends up OUTSIDE the target folder!
  → Known as "Zip Slip" (2018, Snyk)
  → Affects: Java, Python, Go, Ruby zip libraries (unpatched versions)

Detection in Penetration Testing

Systematic Testing:

1. Parameter Identification:
   → All parameters that indicate filenames/paths:
     ?file=, ?page=, ?path=, ?filename=, ?template=, ?doc=,
     ?view=, ?include=, ?load=, ?source=, ?content=, ?show=

2. Payload Progression (from simple to complex):
   Level 1: Simple
   ?file=../../../etc/passwd

   Level 2: URL-encoded
   ?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

   Level 3: Double-encoded
   ?file=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd

   Level 4: Null Byte (Legacy)
   ?file=../../../etc/passwd%00.pdf

   Level 5: OS-specific
   ?file=..\..\..\Windows\win.ini  (Windows)

3. Burp Suite Intruder:
   → Payload position: ?file=§value§
   → Payload list: SecLists/Fuzzing/LFI/LFI-Jhaddix.txt
   → Grep Match: "root:x:", "[drivers]", "DB_PASSWORD"

4. Indicators of Success:
   → Response contains "/bin/bash" or "root:x:0:0:" → /etc/passwd
   → Response contains "[fonts]" → win.ini
   → Error message contains path information: "Failed to open /etc/..."

5. Automated Testing:
   # Nuclei with LFI templates:
   nuclei -u https://target.com -tags lfi

   # dotdotpwn:
   dotdotpwn -m http -h target.com -u "https://target.com/page?file=TRAVERSAL"

Mitigation Measures

Secure implementations by language:

PHP - realpath() for absolute path validation:
  $base = realpath('/var/www/uploads');
  $requested = realpath($base . '/' . $_GET['file']);

  if ($requested === false ||
      strpos($requested, $base) !== 0) {
    die('Access denied');
  }
  // Only here: Read file

  Why realpath() is correct:
  → Resolves all ../ (including URL-encoded ones after decoding)
  → Returns FALSE if file does not exist → Attack detectable
  → Comparison with $base: outside? → DENY!

Python - pathlib for secure path validation:
  from pathlib import Path

  BASE_DIR = Path('/var/www/uploads').resolve()
  requested = (BASE_DIR / user_input).resolve()

  try:
    requested.relative_to(BASE_DIR)  # Raises ValueError if outside!
  except ValueError:
    raise PermissionError('Access denied')

  # Now secure:
  content = requested.read_bytes()

Java - Path.normalize() + startsWith():
  Path base = Paths.get("/uploads").toAbsolutePath().normalize();
  Path requested = base.resolve(userInput).normalize();

  if (!requested.startsWith(base)) {
    throw new SecurityException("Path traversal detected");
  }

Node.js - path.resolve() + startsWith():
  const base = path.resolve('/uploads');
  const requested = path.resolve(base, userInput);

  if (!requested.startsWith(base + path.sep)) {
    return res.status(403).send('Forbidden');
  }

Whitelist approach (most secure):
  // Instead of open paths: only allowed filenames
  const ALLOWED_FILES = {
    'manual': 'manual.pdf',
    'report': 'q4-report.pdf',
  };

  const filename = ALLOWED_FILES[req.query.doc];
  if (!filename) return res.status(400).send('Invalid document');
  res.sendFile(path.join('/uploads', filename));

Operating system level:
  □ Web server process with minimal privileges (no root!)
  □ chroot jail or container isolation
  □ AppArmor/SELinux profiles: Read access only to defined paths
  □ WAF rules: Block "../" and encoding variants
  □ Logging: Alert on file accesses to anomalous paths