Leitlinien

Injektion - Pfadüberquerung

Path Traversal ist eine weitere, recht häufige Art von Injektionsschwachstelle. Sie treten in der Regel auf, wenn bei der Konstruktion eines URI (sei es für eine URL, einen Dateipfad oder anderweitig) nicht ordnungsgemäß sichergestellt wird, dass der vollständig aufgelöste Pfad nicht außerhalb der Wurzel des beabsichtigten Pfads liegt. 

Es ist wichtig, darauf hinzuweisen, dass Path Traversal auch als eine Path *Injection*-Schwachstelle angesehen werden kann. 

Die Auswirkung einer Sicherheitslücke durch Pfadumgehung hängt stark vom Kontext ab, in dem die Umgehung stattfindet, und von der allgemeinen Absicherung, die vorgenommen wurde. Doch bevor wir uns damit befassen, lassen Sie uns ein kurzes praktisches Beispiel für diese Schwachstelle durchgehen, um zu sehen, worüber wir sprechen:                                                                                                        

Eine schnelle Aufschlüsselung

Stellen Sie sich einen Endpunkt in Ihrer Anwendung vor, der Dokumente bereitstellt, z. B. Vorlagen für Verträge oder Jobangebote. Dies könnten alles Dateien wie PDFs sein, die in Ihrer Anwendung statisch sind. 

In diesem Fall könnten Sie einen Code wie den folgenden verwenden, um die Dateien auf Anfrage abzurufen:

let baseFolder = "/var/www/api/documents/";
let path = baseFolder + request.params.filename;

return file.read(path);

Um zu demonstrieren, wie sich die Schwachstelle auswirkt, müssen wir auch wissen, wo sich das Stammverzeichnis unserer Anwendung befindet, also nehmen wir für dieses Beispiel an, dass sich das Stammverzeichnis der Anwendung unter "/var/www/api/" befindet. 

Wir wissen, dass die Anwendung einen "Dateinamen"-Parameter benötigt. Schauen wir uns nun einige Beispiele für Eingaben und das Ergebnis an:

Dateiname Ungelöster Pfad Aufgelöster Pfad
Datenschutz.pdf /var/www/api/documents/Privacy.pdf /var/www/api/documents/Privacy.pdf
../config/prod.config /var/www/api/documents/../config/prod.config /var/www/api/config/prod.config
../../../../etc/shadow /var/www/api/documents/../../../../etc/shadow /etc/shadow

Beachten Sie, dass wir das Dateisystem mit "../" durchlaufen können. Wir können aus dem Ordner "documents", in dem sich die PDFs normalerweise befinden, in den Ordner "/etc/" wechseln, der die Datei "shadow" enthält, die unter Linux Passwort-Hashes enthält. Wie Sie sich vorstellen können, ist das wirklich nicht ideal. 

Blick auf Traversal in Urls

Eine andere Variante der Pfadüberquerung kann bei der Konstruktion von URLs auftreten, die für die Interaktion mit einer API gedacht sind. Angenommen, wir haben eine API mit den folgenden Methoden:

URL-Muster Beschreibung
/api/v1/order/get/{id} Liefert Details über die Bestellung mit der angegebenen ID
/api/v1/order/delete/{id} Löscht einen Auftrag mit einer bestimmten ID

Die API wird von einer anderen Anwendung genutzt, die sie z. B. aufruft, um Informationen über eine Bestellung zu erhalten:

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, je nach der vom Benutzer angegebenen Bestell-ID? Unten sehen Sie die effektive URL, die auf der Grundlage der eingegebenen Daten aufgerufen wird. 

Die Kanonisierung erfolgt in der Regel nicht auf der Client-Seite (obwohl sie möglich ist), aber die Webserver kanonisieren die Anfrage in das unten dargestellte Format.

ID-Nummer der Bestellung Tatsächliche aufgerufene URL
1 /api/v1/bestellung/get/1
1/../../Löschen/1 /api/v1/bestellung/löschen/1

Bei der Eingabe des zweiten Beispiels wird nicht die Bestellung mit der ID-Nummer '1' abgerufen, sondern die Delete-Methode, was natürlich zum Löschen der Bestellung führt.

Abhilfemaßnahmen

Bei der Erörterung von Path Traversal gibt es sowohl direkte Abhilfemaßnahmen als auch indirekte/Verteidigungstechniken, die so oft wie möglich angewendet werden können und sollten. Schauen wir uns zunächst an, wie man mit Pfaden umgeht.

Direkte Milderung

Wenn es um die Behandlung eines Pfades geht, müssen wir den Prozess der Pfadauflösung oder der Kanonisierung von Pfaden und seine Bedeutung verstehen. 

Wenn Sie einen Pfad wie "/var/www/api/documents/../../../../etc/shadow" haben, handelt es sich um einen nicht kanonischen Pfad. Wenn Sie diesen Pfad von Ihrem Dateisystem anfordern, wird er zu "/etc/shadow" kanonisiert. Es ist wichtig, dass Sie nicht versuchen, nicht-kanonische Pfade zu öffnen. Vielmehr sollten Sie Pfade zuerst kanonisieren, überprüfen, ob sie nur auf die gewünschte Datei oder den gewünschten Ordner zeigen, und diese dann lesen. 

let baseFolder = "/var/www/api/documents/";
let path = baseFolder + request.params.filename;

let resolvedPath = path.resolve(path);

if(!resolvedPath.startswith(baseFolder))
return "Versucht, 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 bei der Behandlung von Pfaden ist, immer den kanonischen Pfad zu betrachten. 

Solange der kanonische Pfad nicht gegen irgendwelche Regeln verstößt, macht es keinen Unterschied, wie der Pfad letztendlich aufgebaut ist. Der Versuch, einen Pfad auf diese Weise zu bereinigen, ist sehr fehleranfällig und selten sicher, wenn überhaupt.

Zugang beschränken

In unseren bisherigen Beispielen haben wir die Datei "/etc/shadow" gelesen, die Datei mit den Passwort-Hashes unter Linux. Aber es gibt eigentlich keinen Grund, warum eine Anwendung in der Lage sein sollte, diese Datei oder andere Dateien außerhalb des Stammverzeichnisses zu lesen.

Wenn Sie Container verwenden, haben Sie wahrscheinlich schon viele Risiken minimiert. Maßnahmen zur Absicherung des Containers (nicht als Root laufen lassen usw.) sind unerlässlich. Es wird dringend empfohlen, Ihrem Webprozess alle Privilegien 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 anführen, um die Dinge ein wenig besser zu veranschaulichen, während sie in Aktion sind.

C# - Unsicher

Wenn Sie nicht den vollständigen Pfad auflösen oder sicherstellen, dass Sie nur den Dateinamen als Teil eines Pfades verwenden, bleibt der Code anfällig für Path Traversal. 

var baseFolder = "/var/www/app/documents/";
var fileName = "../../../../../etc/passwd";

// INSECURE: 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));

// SECURE: Weist jeden Leseversuch außerhalb des angegebenen Basisordners zurück.
if(!canonicalPath.StartsWith(baseFolder))
return "Trying to read file outside of base folder";

var fileContents = File.ReadAllText(canonicalPath);

C# - Sicher - Dateiname

In diesem Beispiel schützen wir uns gegen Path Traversal, indem wir nur den Dateinamen als Teil des Pfades verwenden, um sicherzustellen, dass es unmöglich ist, den angegebenen Ordner zu verlassen. 

var baseFolder = "/var/www/app/documents/";

// Verwenden Sie dies nur, wenn Sie die Navigation in andere Unterordner nicht zulassen
var fileName = Path.GetFileName("../../../../../etc/passwd");

// SICHER: Liest /var/www/app/documents/passwd
var fileContents = File.ReadAllText(Path.Combine(baseFolder, fileName));

Java - Unsicher

Wenn Sie nicht den vollständigen Pfad auflösen oder sicherstellen, dass Sie nur den Dateinamen als Teil eines Pfades verwenden, bleibt der Code anfällig für Path Traversal. 

String baseFolder = "/var/www/app/documents/";
String fileName = "../../../../../etc/passwd";

// INSECURE: Reads /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";

// INSECURE: Reads /etc/passwd
Path normalizedPath  = Paths.get(baseFolder + fileName).normalize();
if(!normalizedPath.toString().startsWith(baseFolder))
{
    return "Trying to read path outside of root";
}
else
{
    List<String> lines = Files.readAllLines(normalizedPath);
}

Java - Sicher - Dateiname

In diesem Beispiel schützen wir uns gegen Path Traversal, indem wir nur den Dateinamen als Teil des Pfades verwenden, um sicherzustellen, dass es unmöglich ist, den angegebenen Ordner zu verlassen. 

String baseFolder = "/var/www/app/documents/";

// Only use this if you don't allow navigating into other subfolders
String fileName = Paths.get("../../../../../etc/passwd").getFileName().toString();

// SECURE: Reads /var/www/app/documents/passwd
Path filePath = Paths.get(baseFolder + fileName);
List<String> lines = Files.readAllLines(filePath);

Javascript - Unsicher

Wenn Sie nicht den vollständigen Pfad auflösen oder sicherstellen, dass Sie nur den Dateinamen als Teil eines Pfades verwenden, bleibt der Code anfällig für Path Traversal. 

const fs = require('fs');

const baseFolder = "/var/www/app/documents/";
const fileName = "../../../../../etc/passwd";

// INSECURE: 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));

// SECURE: Liest /var/www/app/documents/passwd
const data = fs.readFileSync(normalizedPath, 'utf8');

Javascript - Sicher - Dateiname

In diesem Beispiel schützen wir uns gegen Path Traversal, indem wir nur den Dateinamen als Teil des Pfades verwenden, um sicherzustellen, dass es unmöglich ist, 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");

// SECURE: Liest /var/www/app/documents/passwd
const data = fs.readFileSync(path.join(baseFolder, fileName), 'utf8');

Python - Unsicher

Wenn Sie nicht den vollständigen Pfad auflösen oder sicherstellen, dass Sie nur den Dateinamen als Teil eines Pfades verwenden, bleibt der Code anfällig für Path Traversal. 

baseFolder = "/var/www/app/documents/"
fileName = "../../../../../etc/passwd"

# INSECURE: 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: Weist jeden Versuch zurück, Dateien außerhalb des angegebenen Basisordners zu lesen
if not normalizedPath.startswith(baseFolder):
return "Trying to read out of base folder"

# SECURE: Liest /var/www/app/documents/passwd
fileContents = open(normalizedPath).read()

Python - Sicher - Dateiname

In diesem Beispiel schützen wir uns gegen Path Traversal, indem wir nur den Dateinamen als Teil des Pfades verwenden, um sicherzustellen, dass es unmöglich ist, 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()