Path Traversal tritt überall dort auf, wo Ihr Code externe Eingaben nutzt, um einen Pfadnamen zu bilden, und nicht verhindert, dass dieser Pfad außerhalb des vorgesehenen Verzeichnisses aufgelöst wird. Der zugehörige Eintrag ist CWE-22. Finden Sie jede dieser Stellen, lassen Sie Benutzer keine Dateinamen mehr vorgeben und prüfen Sie für alle verbleibenden Eingaben den aufgelösten Pfad.
Was ein Angreifer erreicht
Das Lesen von Dateien ist erst der Anfang. Je nachdem, was der Code mit dem Pfad tut, kann ein Angreifer unerwartete Dateien lesen und sensible Daten offenlegen, Programme und Bibliotheken anlegen oder überschreiben und so Code ausführen oder Dateien so weit beschädigen, dass das Produkt nicht mehr funktioniert oder Benutzer ausgesperrt werden. OWASP nennt unter den erreichbaren Dateien unter anderem den Quellcode und die Konfiguration von Anwendungen.
Dateien hinter einem Sicherheitsmechanismus sind die gefährlichsten Ziele. Wer eine Passwortdatei lesen kann, kann Konten per Brute-Force angreifen, und wer ihr ein neues Konto anhängt, kann die Authentifizierung umgehen.
Die Reichweite hängt von der Plattform ab. Unter Windows kann sich ein Angreifer nur innerhalb der Partition bewegen, auf der das Web-Root liegt, unter Linux über die gesamte Festplatte. Die Zugriffskontrollen des Betriebssystems gelten zusätzlich.
Die Stellen finden, an denen Pfade gebaut werden
Beginnen Sie an der Sink, nicht an der URL. Listen Sie jeden Aufruf auf, der eine Datei öffnet, liest, ausliefert, einbindet oder schreibt, und verfolgen Sie jedes Pfadargument bis zu seinem Ursprung zurück. Alles, was mit dem Request in Berührung kommt, gehört auf die Liste, auch Aufrufe hinter eigenen Wrapper-Funktionen und Framework-Helfern. Prüfen Sie daher auch deren Aufrufer.
Die Eingabe ist mehr als nur der Query-String. Zu den OWASP-Beispielen gehören get-files.jsp?file=report.pdf, get-page.php?home=aaa.html und some-page.asp?page=index.html, aber auch ein Cookie. Dieses Snippet ist dem OWASP-Beispiel (ursprünglich Wikipedia) nachempfunden:
$template = 'blue.php';
if ( isset( $_COOKIE['TEMPLATE'] ) )
$template = $_COOKIE['TEMPLATE'];
include ( "/home/users/phpguru/templates/" . $template );
Ein Cookie mit TEMPLATE=../../../../../../../../../etc/passwd bringt include() dazu, bis ins Wurzelverzeichnis zu klettern und die Passwortdatei zurückzugeben. Parameter mit Namen wie file, page, template oder f sind die üblichen Verdächtigen, aber Namen sind kein verlässlicher Anhaltspunkt. Verfolgen Sie die Daten.
Prüfen Sie drei Varianten:
- Relative Traversal:
../-Sequenzen, die aus dem vorgesehenen Verzeichnis herausführen. - Absolute Pfade:
?f=/etc/passwdoder?f=/var/www/html/get.php, die funktionieren, wenn die Eingabe als vollständiger Pfad verwendet wird. - Remote Includes:
?page=http://other-site/..., wenn der Include-Mechanismus URLs akzeptiert.
Ausführliche Fehlermeldungen helfen dem Angreifer, weil sie echte Pfade preisgeben, an denen er sich orientieren kann. Prüfen Sie, was Ihre Anwendung zurückgibt, wenn eine Datei fehlt.
Warum Blocklisten scheitern
Das Entfernen von ../ funktioniert nicht, weil dieselbe Traversal viele Schreibweisen hat:
%2e%2e%2f,..%2fund%2e%2e/bedeuten alle../...%5cund%2e%2e%5cbedeuten..\. Das ist unter Windows relevant, wo beide Trennzeichen funktionieren.%252e%252e%255cist die doppelt kodierte Form von..\. Web-Container dekodieren nur einmal, sodass die Eingabe noch kodiert ankommt. Wenn Ihr Code oder eine Bibliothek sie erneut dekodiert, wird daraus eine Traversal, nachdem die Prüfung bereits gelaufen ist...%c0%afund..%c1%9csind weitere Kodierungen von../und..\.- Ein Nullbyte wie in
?file=secret.doc%00.pdfkann dazu führen, dass eine Java-Anwendung.pdfsieht, während das Betriebssystem.docsieht. Angreifer nutzen das, um die Validierung zu umgehen.
Auch das Filtern selbst schlägt fehl. Entfernt man ../ sequenziell aus .../...//, bilden die übrig gebliebenen Zeichen erneut ../. Nur / zu filtern ist nutzlos, wenn das Dateisystem \ akzeptiert.
Maßnahme eins: keine Pfade mehr akzeptieren
Die beste Kontrolle ist, Benutzereingaben aus dem Pfad zu entfernen. Verwenden Sie Indizes statt Teilen von Dateinamen. Das OWASP-Beispiel ist eine Sprachdatei: Der Benutzer sendet 5, und der Server ordnet das der Sprache zu, anstatt einem zurückgesendeten Namen zu vertrauen.
Speichern Sie Dateien für Downloads unter einer ID und liefern Sie sie per ID aus, sodass der Benutzer weder einen Namen noch eine Dateiendung angibt. Das Template-Beispiel sieht dann so aus:
$templates = [1 => 'blue.php', 2 => 'red.php'];
$id = (int) ($_COOKIE['TEMPLATE'] ?? 1);
$template = $templates[$id] ?? 'blue.php';
include '/home/users/phpguru/templates/' . $template;
Wenn Sie einen Namen akzeptieren müssen, validieren Sie ihn gegen bekannte gültige Werte und weisen Sie alles andere ab. Verwenden Sie eine strikte Allowlist für Zeichen, höchstens einen . im Dateinamen, keine Verzeichnistrenner und eine Liste zulässiger Dateiendungen. Weisen Sie fehlerhafte Eingaben ab, statt zu versuchen, sie zu bereinigen.
Maßnahme zwei: kanonisieren, dann prüfen
Wenn Eingaben eine Datei-API erreichen müssen, dekodieren Sie sie einmal und lösen Sie sie vor der Validierung mit einer eingebauten Kanonisierungsfunktion auf:
| Sprache | Funktion |
|---|---|
| C | realpath() |
| Java | getCanonicalPath() |
| ASP.NET | GetFullPath() |
| Perl | realpath() oder abs_path() |
| PHP | realpath() |
Diese Funktionen entfernen ..-Sequenzen und symbolische Links. Bestätigen Sie nach dem Auflösen, dass das Ergebnis weiterhin in Ihrem Basisverzeichnis liegt, und lehnen Sie die Anfrage ab, wenn das nicht der Fall ist. Eine PHP-Skizze, bei der $name die Allowlist bereits passiert hat:
$base = realpath('/home/users/phpguru/templates');
$full = realpath($base . '/' . $name);
if ($full === false || strpos($full, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(404);
exit;
}
Führen Sie jede Prüfung auf dem Server durch. Clientseitige Validierung lässt sich trivial umgehen.
Schaden begrenzen
Halten Sie sensible Konfigurationsdateien außerhalb des Web-Roots. Legen Sie unter Windows IIS das Web-Root nicht auf die Systemfestplatte, damit eine Traversal keine Systemverzeichnisse erreicht. Chroot-Jails und Code Access Policies schränken ein, wo Dateien gelesen oder geschrieben werden können, und begrenzen den Schaden, wenn eine Prüfung übersehen wurde.
Spielen Sie zum Testen die oben genannten Kodierungen gegen jeden gefundenen Parameter ab, dazu einen absoluten Pfad und eine Remote-URL. Ein Penetrationstest erledigt das systematisch.
Wo Sie anfangen
- Listen Sie jeden dateibezogenen Aufruf in Ihrem Code auf, ergänzen Sie die Wrapper und Framework-Helfer darum und verfolgen Sie jedes Pfadargument bis zu seiner Quelle zurück, einschließlich Cookies und Headern.
- Ersetzen Sie vom Benutzer gelieferte Dateinamen durch IDs, die auf dem Server Dateien zugeordnet sind. Beginnen Sie mit Downloads sowie der Auswahl von Templates und Sprachen.
- Dekodieren Sie in allen verbleibenden Fällen einmal, kanonisieren Sie mit der Plattformfunktion und weisen Sie alles ab, was außerhalb des Basisverzeichnisses aufgelöst wird.
- Löschen Sie Blocklist-Code, der
../oder/entfernt, und ersetzen Sie ihn durch eine Allowlist für Zeichen, Dateiendungen und, wo möglich, Werte. - Verlagern Sie sensible Konfiguration aus dem Web-Root und geben Sie keine Fehlermeldungen mehr zurück, die Dateipfade verraten.
- Testen Sie jeden Parameter mit den kodierten, doppelt kodierten, absoluten und Remote-Varianten und behalten Sie die Fälle als Regressionstests.
