Back

Verhinderung von Path-Traversal-Angriffen in Node.js

Verhinderung von Path-Traversal-Angriffen in Node.js

Wenn Ihre Express-App Dateien basierend auf Benutzereingaben bereitstellt – eine Download-Route, eine Upload-Vorschau, ein dynamisches Asset – haben Sie eine potenzielle Path-Traversal-Schwachstelle. Es ist einer der häufigsten und schädlichsten Dateisicherheitsfehler in Node.js, und er lässt sich überraschend leicht einführen, ohne es zu bemerken.

Dieser Artikel erklärt, wie Path-Traversal-Angriffe funktionieren, warum gängige „Lösungen” zu kurz greifen und wie sichere Dateiverarbeitung in Node.js in der Praxis tatsächlich aussieht.

Wichtigste Erkenntnisse

  • Path-Traversal-Angriffe nutzen fehlende oder unvollständige Validierung von benutzerdefinierten Dateipfaden aus und ermöglichen Angreifern den Zugriff auf Dateien außerhalb der vorgesehenen Verzeichnisse.
  • path.normalize() und path.join() sind keine Sicherheitswerkzeuge – sie bereinigen Pfade, verhindern aber keine Verzeichnisdurchquerung.
  • Die korrekte Verteidigung besteht darin, Pfade mit path.resolve() aufzulösen und zu überprüfen, ob das Ergebnis innerhalb eines zulässigen Basisverzeichnisses bleibt, indem eine startsWith-Prüfung verwendet wird, die path.sep einschließt.
  • Der sicherste Ansatz besteht darin, benutzerdefinierte Pfade vollständig zu vermeiden, indem Eingaben über eine ID-basierte Zuordnung auf Dateien abgebildet werden.

Was ist ein Path-Traversal-Angriff?

Ein Path-Traversal-Angriff (auch Directory Traversal genannt) tritt auf, wenn ein Angreifer eine Dateipfad-Eingabe manipuliert, um auf Dateien außerhalb des vorgesehenen Verzeichnisses zuzugreifen. Das klassische Beispiel:

GET /download?file=../../etc/passwd

Wenn Ihr Server diese Eingabe naiv mit einem Basisverzeichnis verbindet und die Datei liest, haben Sie gerade die Passwortdatei Ihres Systems preisgegeben.

Der Angriff ist nicht ausgeklügelt. Er nutzt fehlende oder unvollständige Eingabevalidierung aus – insbesondere das Versäumnis zu überprüfen, ob ein aufgelöster Pfad innerhalb eines zulässigen Verzeichnisses bleibt.

Warum path.normalize() und path.join() nicht ausreichen

Dies ist das Wichtigste, was Sie über die Verhinderung von Path-Traversal in Node.js verstehen müssen: Diese Hilfsfunktionen sind keine Sicherheitswerkzeuge.

path.normalize() löst ..-Sequenzen auf und bereinigt redundante Schrägstriche. path.join() verkettet Segmente. Keine der beiden Funktionen verhindert Traversierung – sie erzeugen nur einen saubereren Pfad, der möglicherweise immer noch außerhalb Ihres vorgesehenen Verzeichnisses zeigt.

Betrachten Sie dieses häufige, aber unsichere Muster:

// ❌ Unsicher: path.join verhindert keine Traversierung
app.get('/download', (req, res) => {
  const filePath = path.join(__dirname, 'uploads', req.query.file)
  res.sendFile(filePath)
})

Wenn req.query.file den Wert ../../etc/passwd hat, löst path.join() ihn sauber auf – und sendet die falsche Datei.

URL-Kodierung verschlimmert die Situation. Ein Angreifer könnte ..%2F..%2Fetc%2Fpasswd senden. Express dekodiert URL-Parameter automatisch, daher sollten Sie den dekodierten Wert validieren, den Sie erhalten. Wenn Sie rohe URLs selbst verarbeiten, dekodieren Sie einmal (mit ordnungsgemäßer Fehlerbehandlung) vor der Validierung.

Das korrekte Muster: Auflösen und Containment überprüfen

Der zuverlässige Ansatz für sichere Dateiverarbeitung in Node.js ist:

  1. Lösen Sie den vom Benutzer bereitgestellten Pfad gegen Ihr Basisverzeichnis mit path.resolve() auf.
  2. Überprüfen Sie, ob der aufgelöste Pfad innerhalb dieses Verzeichnisses bleibt.
const path = require('path')

const BASE_DIR = path.resolve(__dirname, 'uploads')

function safeResolve(userInput) {
  const resolved = path.resolve(BASE_DIR, userInput)

  // Sicherstellen, dass der aufgelöste Pfad innerhalb von BASE_DIR liegt
  if (!resolved.startsWith(BASE_DIR + path.sep) && resolved !== BASE_DIR) {
    return null
  }

  return resolved
}

// ✅ Sichere Express-Datei-Download-Route
app.get('/download', (req, res) => {
  const safePath = safeResolve(req.query.file)

  if (!safePath) {
    return res.status(403).send('Access denied')
  }

  res.sendFile(safePath)
})

Beachten Sie die Hinzufügung von path.sep. Ohne diese würde ein Basisverzeichnis /uploads fälschlicherweise zulassen, dass /uploads-other/secret.txt die Präfixprüfung besteht.

Unter Windows behandeln path.resolve() und path.sep Backslashes korrekt, sodass dieses Muster plattformübergreifend funktioniert. Für strengere Validierung können Sie auch Pfade mit path.relative() vergleichen und jedes Ergebnis ablehnen, das aus dem Basisverzeichnis ausbricht.

Beachten Sie, dass Symlinks immer noch außerhalb Ihres Basisverzeichnisses zeigen können. Wenn Sie sensible Dateien bereitstellen, lösen Sie echte Pfade mit fs.realpath() auf, bevor Sie sie senden.

Noch besser: Benutzerdefinierte Pfade vollständig vermeiden

Der sicherste Ansatz besteht darin, benutzerdefinierte Pfade überhaupt nicht zu verwenden. Bilden Sie stattdessen Benutzereingaben indirekt auf Dateien ab:

// ✅ ID-basierte Dateisuche – keine Pfadkonstruktion aus Benutzereingaben
const FILES = {
  'report-2024': 'reports/annual-2024.pdf',
  'invoice-001': 'invoices/inv-001.pdf',
}

app.get('/download/:id', (req, res) => {
  const filePath = FILES[req.params.id]

  if (!filePath) {
    return res.status(404).send('Not found')
  }

  res.sendFile(path.resolve(__dirname, 'secure', filePath))
})

Dies eliminiert das Traversal-Risiko vollständig. Wenn Ihr Anwendungsfall es zulässt – Datei-Downloads, Dokumentexporte, benutzerspezifische Assets – ist dies das Muster, das Sie zuerst anstreben sollten.

Ein Hinweis zur statischen Bereitstellung in Express

express.static() ist im Allgemeinen sicherer für die Bereitstellung öffentlicher Assets, da es den Zugriff auf ein definiertes Root-Verzeichnis beschränkt. Das Risiko entsteht, wenn Sie benutzerdefinierte Datei-Bereitstellungsrouten schreiben, die Pfade aus Request-Parametern konstruieren oder unbeabsichtigte Verzeichnisse offenlegen. Konfiguration und Root-Auswahl sind dennoch wichtig.

Fazit

Path Traversal ist eine unkomplizierte Schwachstelle mit einer unkomplizierten Lösung: Vertrauen Sie niemals Benutzereingaben als Dateipfad. Lösen Sie ihn gegen eine feste Basis auf, überprüfen Sie das Containment mit path.resolve() und einer startsWith-Prüfung, die path.sep einschließt, und validieren Sie dekodierte Eingaben vor der Verwendung. Bevorzugen Sie nach Möglichkeit ID-basierte Lookups, die die Pfadkonstruktion aus Benutzereingaben umgehen. path.normalize() und path.join() sind nützliche Hilfsfunktionen – nur keine Sicherheitsgarantien.

FAQs

Es hilft, indem es Dateien aus einem definierten Root-Verzeichnis bereitstellt und die Pfadauflösung intern behandelt, ist aber keine Garantie für vollständige Sicherheit. Fehlkonfiguration oder das Offenlegen sensibler Verzeichnisse können immer noch Risiken schaffen. Benutzerdefinierte Datei-Bereitstellungsrouten, die Benutzereingaben verwenden, sind der Ort, an dem die meisten Schwachstellen auftreten.

Nein. Das Filtern oder Ablehnen von Zeichenfolgen, die Punkt-Punkt-Sequenzen enthalten, ist fragil. Angreifer können solche Prüfungen mit URL-Kodierung, doppelter Kodierung oder plattformspezifischen Pfadtrennzeichen umgehen. Die zuverlässige Verteidigung besteht darin, den vollständigen Pfad mit path.resolve() aufzulösen und dann zu überprüfen, ob das Ergebnis innerhalb Ihres vorgesehenen Basisverzeichnisses liegt, indem Sie eine startsWith-Prüfung oder path.relative() verwenden.

Ja. Die Verwendung von path.resolve() und path.sep macht die Containment-Prüfung plattformübergreifend funktionsfähig. path.resolve() normalisiert Windows-Pfade, und path.sep wird zum korrekten Trennzeichen ausgewertet. Für strengere Validierung verwenden Sie path.relative(), um sicherzustellen, dass der aufgelöste Pfad nicht aus dem Basisverzeichnis ausbricht.

Ja. Nachdem Sie bestätigt haben, dass der aufgelöste Pfad innerhalb Ihres Basisverzeichnisses liegt, überprüfen Sie mit fs.existsSync() oder fs.access(), ob die Datei existiert, bevor Sie res.sendFile() aufrufen. Dies verhindert das Durchsickern von Informationen über Ihre Verzeichnisstruktur durch Fehlermeldungen und gibt Ihnen die Kontrolle über die Antwort, wenn eine Datei fehlt.

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay