Injection – Path Traversal
Path Traversal ist eine weitere recht verbreitete Art der Injection-Schwachstelle. Sie tritt meist dann auf, wenn bei der Erstellung eines URI (sei es für eine URL, einen Dateipfad oder Ähnliches) nicht ordnungsgemäß sichergestellt wird, dass der vollständig aufgelöste Pfad nicht außerhalb des beabsichtigten Stammverzeichnisses liegt.
Es ist wichtig zu erwähnen, dass Path Traversal eigentlich auch als eine Form der Pfad-*Injection*-Schwachstelle betrachtet werden kann.
Die Auswirkungen einer Pfad-Traversal-Schwachstelle hängen stark vom Kontext ab, in dem die Traversal auftritt, sowie von der allgemeinen Absicherung des Systems. Bevor wir jedoch darauf eingehen, lassen Sie uns anhand eines kurzen Praxisbeispiels verdeutlichen, worum es bei dieser Schwachstelle geht:
Eine kurze Analyse
Stellen Sie sich einen Endpunkt in Ihrer Anwendung vor, der Dokumente bereitstellt, wie etwa Vorlagen für Verträge oder Stellenangebote. Dabei kann es sich um statische Dateien in Ihrer Anwendung handeln, beispielsweise PDFs.
In diesem Fall könnte ein Code-Abschnitt zum Abrufen der Dateien bei einer Anfrage wie folgt aussehen:
let baseFolder = "/var/www/api/documents/";
let path = baseFolder + request.params.filename;
return file.read(path);
Um zu demonstrieren, wie die Schwachstelle ausgenutzt wird, müssen wir wissen, wo sich das Stammverzeichnis unserer Anwendung befindet. Nehmen wir für dieses Beispiel an, das Root-Verzeichnis der Anwendung liegt unter „/var/www/api/“.
Wir wissen, dass die Anwendung einen „filename“-Parameter entgegennimmt. Schauen wir uns einige Beispiele für Eingaben und deren Ergebnisse an:
Beachten Sie, wie wir mithilfe von „../“ durch das Dateisystem navigieren können. Wir sind in der Lage, den „documents“-Ordner, in dem die PDFs normalerweise liegen, zu verlassen und in den „/etc/“-Ordner zu gelangen, der die „shadow“-Datei enthält – diese speichert unter Linux Passwort-Hashes. Wie Sie sich vorstellen können, ist das alles andere als ideal.
Traversal in URLs betrachten
Eine weitere Variante der Pfad-Traversal kann auftreten, wenn URLs erstellt werden, die mit einer API interagieren sollen. Angenommen, wir haben eine API mit den folgenden Methoden:
Die API wird von einer anderen Anwendung aufgerufen, die beispielsweise Informationen zu einer Bestellung abrufen möchte:
let apiBase = "https://my.api/api/v1";
let orderApi = apiBase + "/order/get";
let apiUrl = orderApi + request.params.orderId;
let response = http.get(apiUrl);
Was passiert nun, abhängig von der vom Benutzer bereitgestellten Bestell-ID? Unten sehen Sie die effektive URL, die basierend auf der bereitgestellten Eingabe aufgerufen wird.
Die Normalisierung (Canonicalization) erfolgt normalerweise nicht auf der Client-Seite (obwohl dies möglich wäre), aber Webserver normalisieren die Anfrage in das unten gezeigte Format.
Bei der Eingabe im zweiten Beispiel rufen wir nicht die Bestellung mit der ID „1“ ab, sondern führen stattdessen die Löschmethode aus, was natürlich dazu führt, dass die Bestellung gelöscht wird.
Gegenmaßnahmen
Bei der Behandlung von Pfad-Traversal-Schwachstellen gibt es sowohl direkte Abhilfemaßnahmen als auch indirekte Verteidigungstechniken, die so oft wie möglich angewendet werden sollten. Sehen wir uns zunächst an, wie man mit Pfaden umgeht.
Direkte Abhilfemaßnahme
Beim Umgang mit Pfaden müssen wir den Prozess der Pfadauflösung, auch Pfad-Kanonisierung genannt, und dessen Bedeutung verstehen.
Ein Pfad wie „/var/www/api/documents/../../../../etc/shadow“ ist ein nicht-kanonischer Pfad. Wenn Sie diesen Pfad bei Ihrem Dateisystem anfordern, wird er zu „/etc/shadow“ kanonisiert. Es ist entscheidend, dass Sie nicht versuchen, nicht-kanonische Pfade zu öffnen. Stattdessen sollten Sie Pfade zuerst kanonisieren, überprüfen, ob sie nur auf die beabsichtigte Datei oder den beabsichtigten Ordner verweisen, und sie erst dann lesen.
let baseFolder = "/var/www/api/documents/";
let path = baseFolder + request.params.filename;
let resolvedPath = path.resolve(path);
if(!resolvedPath.startswith(baseFolder))
return "Versuch, außerhalb des Basisordners zu lesen";
else
return file.read(resolvedPath);
Anti-Pattern – Versuch, Dateinamen zu bereinigen
Es mag verlockend sein, so etwas zu tun:
let baseFolder = "/var/www/api/documents/";
let path = baseFolder + request.params.filename.replace("../", "");
...
Dieser Ansatz sollte jedoch nicht verwendet werden. Der Schlüssel beim Umgang mit Pfaden liegt darin, immer den kanonischen Pfad zu betrachten.
Solange der kanonische Pfad keine Regeln verletzt, spielt es keine Rolle, wie der Pfad letztendlich konstruiert ist. Der Versuch, einen Pfad auf diese Weise zu bereinigen, ist sehr fehleranfällig und selten, wenn überhaupt, sicher.
Zugriff einschränken
In unseren vorherigen Beispielen haben wir das Lesen der Datei „/etc/shadow“ verwendet, die unter Linux die Passwort-Hashes enthält. Es gibt jedoch keinen Grund, warum eine Anwendung in der Lage sein sollte, diese Datei oder andere Dateien außerhalb ihres Stammverzeichnisses zu lesen.
Wenn Sie Container einsetzen, mindern Sie wahrscheinlich bereits viele Risiken. Es ist wichtig, Schritte zur Härtung des Containers zu unternehmen (z. B. nicht als Root ausführen). Es wird dringend empfohlen, alle Privilegien Ihres Webprozesses zu entziehen und seine Leseberechtigungen im Dateisystem auf die Dateien zu beschränken, die er unbedingt benötigt.
Beispiele
Wir werden nun einige Beispiele in verschiedenen Sprachen teilen, um die Funktionsweise besser zu veranschaulichen.
C# – Unsicher
Wenn der vollständige Pfad nicht aufgelöst wird oder nicht sichergestellt ist, dass nur der Dateiname eines Pfads verwendet wird, ist der Code anfällig für Path Traversal.
var baseFolder = "/var/www/app/documents/";
var fileName = "../../../../../etc/passwd";
// UNSICHER: Liest /etc/passwd
var fileContents = File.ReadAllText(Path.Combine(baseFolder, fileName));
C# – Sicher – kanonisch
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir den vollständigen (absoluten) Pfad auflösen und sicherstellen, dass der aufgelöste Pfad innerhalb unseres Basisordners liegt.
var baseFolder = "/var/www/app/documents/";
var fileName = "../../../../../etc/passwd";
var canonicalPath = Path.GetFullPath(Path.Combine(baseFolder, fileName));
// SICHER: Lehnt jeden Versuch ab, außerhalb der angegebenen Basis zu lesen.
if(!canonicalPath.StartsWith(baseFolder))
return "Versuch, Datei außerhalb des Basisordners zu lesen";
var fileContents = File.ReadAllText(canonicalPath);
C# – Sicher – Dateiname
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir nur den Dateinamen aus dem Pfad verwenden. Dadurch ist es unmöglich, den angegebenen Ordner zu verlassen.
var baseFolder = "/var/www/app/documents/";
// Nur verwenden, wenn das Navigieren in Unterordner nicht erlaubt ist
var fileName = Path.GetFileName("../../../../../etc/passwd");
// SICHER: Liest /var/www/app/documents/passwd
var fileContents = File.ReadAllText(Path.Combine(baseFolder, fileName));
Java - Unsicher
Wenn der vollständige Pfad nicht aufgelöst wird oder nicht sichergestellt ist, dass nur der Dateiname eines Pfads verwendet wird, ist der Code anfällig für Path Traversal.
String baseFolder = "/var/www/app/documents/";
String fileName = "../../../../../etc/passwd";
// UNSICHER: Liest /etc/passwd
Path filePath = Paths.get(baseFolder + fileName);
List<String> lines = Files.readAllLines(filePath);
Java – Sicher – Kanonisch
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir den vollständigen (absoluten) Pfad auflösen und sicherstellen, dass der aufgelöste Pfad innerhalb unseres Basisordners liegt.
String baseFolder = "/var/www/app/documents/";
String fileName = "../../../../../etc/passwd";
// UNSICHER: Liest /etc/passwd
Path normalizedPath = Paths.get(baseFolder + fileName).normalize();
if(!normalizedPath.toString().startsWith(baseFolder))
{
return "Versuch, auf einen Pfad außerhalb des Root-Verzeichnisses zuzugreifen";
}
else
{
List<String> lines = Files.readAllLines(normalizedPath);
}
Java – Sicher – Dateiname
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir nur den Dateinamen aus dem Pfad verwenden. Dadurch ist es unmöglich, den angegebenen Ordner zu verlassen.
String baseFolder = "/var/www/app/documents/";
// Nur verwenden, wenn das Navigieren in Unterordner nicht erlaubt ist
String fileName = Paths.get("../../../../../etc/passwd").getFileName().toString();
// SICHER: Liest /var/www/app/documents/passwd
Path filePath = Paths.get(baseFolder + fileName);
List<String> lines = Files.readAllLines(filePath);
Javascript – Unsicher
Wenn der vollständige Pfad nicht aufgelöst wird oder nicht sichergestellt ist, dass nur der Dateiname eines Pfads verwendet wird, ist der Code anfällig für Path Traversal.
const fs = require('fs');
const baseFolder = "/var/www/app/documents/";
const fileName = "../../../../../etc/passwd";
// UNSICHER: Liest /etc/passwd
const data = fs.readFileSync(baseFolder + fileName, 'utf8');
Javascript – Sicher – Kanonisch
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir den vollständigen (absoluten) Pfad auflösen und sicherstellen, dass der aufgelöste Pfad innerhalb unseres Basisordners liegt.
const fs = require("fs");
const path = require("path");
const baseFolder = "/var/www/app/documents/";
const fileName = "../../../../../etc/passwd";
const normalizedPath = path.normalize(path.join(baseFolder, fileName));
// SICHER: Liest /var/www/app/documents/passwd
const data = fs.readFileSync(normalizedPath, 'utf8');
Javascript – Sicher – Dateiname
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir nur den Dateinamen aus dem Pfad verwenden. Dadurch ist es unmöglich, den angegebenen Ordner zu verlassen.
const fs = require("fs");
const path = require("path");
const baseFolder = "/var/www/app/documents/";
const fileName = path.basename("../../../../../etc/passwd");
// SICHER: Liest /var/www/app/documents/passwd
const data = fs.readFileSync(path.join(baseFolder, fileName), 'utf8');
Python – Unsicher
Wenn der vollständige Pfad nicht aufgelöst wird oder nicht sichergestellt ist, dass nur der Dateiname eines Pfads verwendet wird, ist der Code anfällig für Path Traversal.
baseFolder = "/var/www/app/documents/"
fileName = "../../../../../etc/passwd"
# UNSICHER: Liest /etc/passwd
fileContents = open(baseFolder + fileName).read()
Python – Sicher – Kanonisch
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir den vollständigen (absoluten) Pfad auflösen und sicherstellen, dass der aufgelöste Pfad innerhalb unseres Basisordners liegt.
import os.path
baseFolder = "/var/www/app/documents/"
fileName = "../../../../../etc/passwd"
normalizedPath = os.path.normpath(baseFolder + fileName)
# SICHER: Blockiert jeden Versuch, Dateien außerhalb des angegebenen Basisordners zu lesen
if not normalizedPath.startswith(baseFolder):
return "Versuch, außerhalb des Basisordners zu lesen"
# SICHER: Liest /var/www/app/documents/passwd
fileContents = open(normalizedPath).read()
Python – Sicher – Dateiname
In diesem Beispiel schützen wir uns vor Path Traversal, indem wir nur den Dateinamen aus dem Pfad verwenden. Dadurch ist es unmöglich, den angegebenen Ordner zu verlassen.
import os.path
baseFolder = "/var/www/app/documents/"
fileName = os.path.basename("../../../../../etc/passwd")
# SICHER: Liest /var/www/app/documents/passwd
fileContents = open(os.path.join(baseFolder, fileName)).read()
Sichere Programmierung in der Praxis
Entdecken Sie Richtlinien für sicheres Programmieren, um Schwachstellen wie die OWASP Top 10 zu verstehen und zu beheben, und tauchen Sie in geführte Trainingsmissionen ein, um diese in realistischen Anwendungssimulationen zu üben.