1 Punkte von GN⁺ 2023-07-04 | 1 Kommentare | Auf WhatsApp teilen
  • Nginx, ein vielseitiger Webserver mit dominierendem Marktanteil seit 2004
  • Fehlkonfigurationen von Nginx können zu Sicherheitslücken und Datenlecks führen
  • Vorstellung von NavGix, einem automatisierten Tool zur Erkennung von Schwachstellen in Nginx
  • Über diese Schwachstelle kann auf unbeabsichtigte Bereiche von Dateien und Verzeichnissen zugegriffen werden
  • Fallstudien zu Bitwarden und Googles HPC Toolkit zeigen die Schwere dieser Schwachstelle

1 Kommentare

 
GN⁺ 2023-07-04
Meinungen auf Hacker News
  • Zur Info: gixy (nginx-Konfigurationsprüfer) erkennt dieses Problem: https://github.com/yandex/gixy/blob/master/docs/en/plugins/a...
    Und NixOS führt gixy automatisch für Konfigurationen aus, die darüber erzeugt wurden, und verweigert den System-Build, wenn es Probleme gibt.

    • Wenn ein Webserver zusätzliche Tools erfordert, damit Nutzer solche Fallen vermeiden, sollte man vielleicht die Defaults noch einmal überdenken.
    • Ich kannte gixy nicht, habe es auf meinem Heimserver laufen lassen und es hat eine Schwachstelle gefunden. Ich habe $uri in einem 301-Redirect verwendet.
    • Ich habe Nix einmal ausprobiert, und bisher sieht es ziemlich gut aus.
      Ich frage mich aber, ob es einen besseren Optionen-Sucher gibt. Die Suche über alle Tausenden Optionen fand ich nicht so gut; ich möchte nur ein Paket wie ssh ansehen und die dazugehörigen Optionen sehen, aber es werden viel zu viele irrelevante Ergebnisse eingemischt.
    • NixOS führt Gixy nicht mehr aus. Siehe https://github.com/NixOS/nixpkgs/pull/209075
  • Vielleicht eine dumme Frage, aber gibt es einen guten Grund, warum nginx erlauben sollte, in URL-Pfaden mit .. in übergeordnete Verzeichnisse zu wechseln? Das wirkt einfach wie ein Verhalten, das nur auf Probleme wartet.
    Edit: Ich bin etwas verwirrt darüber, was bei der ursprünglichen Schwachstelle passiert. Es scheint, als würde http://localhost/foo../secretfile.txt in etwas wie /var/www/foo/../secretfile.txt aufgelöst. Warum würde ein nicht verwundbarer Server dann http://localhost/foo/../secretfile.txt nicht genauso auflösen? Ich verstehe nicht, warum .. im Pfad nur manchmal funktioniert.

    • Das ist ein in nginx schon sehr lange bekanntes Problem und ein gängiger Angriffsvektor in CTFs: https://book.hacktricks.xyz/network-services-pentesting/pent...
    • Das Problem ist, dass eine URL kein tatsächlicher Pfad ist. Eine URL ist eine abstrakte Adresse für eine Ressource, die ein Verzeichnis, eine Datei, eine ausführbare Datei, ein Stream usw. sein kann.
      In diesem Fall wird ein Teil der URL aufgrund der Art, wie er in der Konfiguration auf das lokale Dateisystem gemappt wird, von nginx als Verzeichnis (http://localhost/foo) interpretiert. Weil das auf ein Verzeichnis zu zeigen scheint, entsteht beim Erstellen des vollständigen Pfads zur angefragten Ressource durch nginx "${mapped_path}/../secretfile.txt"; aus URL-Sicht ist das unsinnig, im lokalen Dateisystem aber gültig. Eine URL ist nur ein String, keine echten Pfadbestandteile, daher ist auch die Position von Slashes nicht grundsätzlich bedeutsam.
      Seit es das Web gibt, ist das ein sehr häufiges Problem bei Webservern. URLs direkt auf Dateipfade zu mappen war beliebt, weil es mit einfachen Dateiservern mit Index begann, aber bald wurde daraus eine gemischte Umgebung, in der URLs keine Pfade mehr waren, sondern Anwendungs-IDs: Ein Teil des Pfads gibt die App an, der Rest wird als Parameter oder Argumente betrachtet.
      Und generell ergibt es für Dateisystemobjekte meist keinen Sinn, . oder .. in URLs zu respektieren. Meine Apps bereinigen den Request-Pfad, damit er korrekt gemappt wird. Browser behandeln URLs beim Erzeugen relativer Links wie Pfade, daher muss man auch vorsichtig sein, wann und wie man ein abschließendes / verwendet. Serverseitig kann das auf Ressourcen mit anderer Bedeutung verweisen.
    • Für „normale“ Use Cases braucht man das nicht. Es wäre wohl besser, in location ein Flag wie allow_parent_traversal on; zu haben und das zu einem Verhalten zu machen, das man explizit einschalten muss.
    • Ich vermute, NginX prüft wahrscheinlich "/foo/bar/.." und verbietet es oder normalisiert es zu "/foo/". Aber "/foo/bar.." ist ein völlig gültiger Dateiname, daher scheint es an solchen Prüfungen vorbeizukommen.
    • Wann .. in einem Pfad funktioniert, hängt vollständig von den Dateiberechtigungen ab.
      Nehmen wir in diesem Fall an, dass man vom Web-Indexverzeichnis (../index.html) bis zum Root-Verzeichnis (/) Leserechte hat. Da man bis zum Root hinabsteigen darf, kann man nun alle von Root aus erreichbaren world-readable Dateien sehen, zum Beispiel /etc/passwd.
      Stell dir eine dreizinkige Gabel vor: Der Webserver sitzt auf dem ganz rechten Zinken. Wenn der Griff, an dem die Zinken zusammenlaufen, das Dateisystem ist, und man vom rechten Zinken, auf dem der Webserver sitzt, Zugriffsrechte auf die Dateien und Verzeichnisse bis zum Griff hat, kann man nach Erreichen des Griffs auch weiter auf Dateien in den anderen Zinken zugreifen.
  • Ich verstehe nicht, warum das nicht als nginx-Schwachstelle gilt. Dieses Verhalten ist völlig absurd, scheint keinem nützlichen Zweck zu dienen und ist unmittelbar ausnutzbar.

    • Das wurde wegen der Geschwindigkeit so gemacht. Einfache Textersetzung ist viel schneller, als zu prüfen, ob ein Pfad korrekt mit einem Slash endet.
      Man sollte auch bedenken, dass Nginx durch Benchmarks populär wurde, die es als „webskalierbarer“ als Apache2 darstellten.
  • Mir kam folgender Gedanke: Man könnte im Dateinamen-Parser des Linux-Kernels eine Option ähnlich einer Linux Capability einführen, die die Option .. entfernt.
    In Web-Apps gibt es seit den Zeiten von Telefonmodems immer wieder Umgehungen, bei denen irgendwie zwei Punkte eingeschleust werden. So wie der Linux-Kernel es bei verschiedenen anderen Klassen von Userspace-Bugs getan hat, ist es jetzt an der Zeit, einen Weg zu finden, dieses Problem auf einen Schlag zu schließen.

    • Es gibt RESOLVE_BENEATH unter https://man7.org/linux/man-pages/man2/openat2.2.html
      FreeBSD hat diese Funktion im normalen openat(2) als O_RESOLVE_BENEATH.
    • Das würde viel zu viele Dinge kaputtmachen und wäre nichts, was man vernünftigerweise tun sollte.
      Man kann nginx als separaten Benutzer mit sehr eingeschränkten Rechten ausführen oder in Docker laufen lassen. Zusammen mit regelmäßigen Updates löst das normalerweise 90 % der Sicherheitsprobleme.
    • /some/../path sollte nahezu zu 100 % verboten werden. Es gibt keinen sinnvollen Use Case dafür, außer „jemand hat hässlichen Code geschrieben“.
      ../some/path ergibt zumindest gelegentlich Sinn.
      Allerdings wäre das vermutlich nicht so nützlich, wie man denkt, weil viele Apps .. bereits auflösen, bevor sie es an das Betriebssystem übergeben.
    • Macht kaum einen Unterschied. Code normalisiert Pfade oft, bevor er die Dateisystem-API erreicht.
    • Auf Kernel-Seite gibt es ein anderes System, nämlich das Rechtesystem, auf das wir uns verlassen.
      Wenn man Dateien aus einem Ordner über das Web ausliefert, muss das Web-Framework sicherstellen, dass man nicht aus dem vorgesehenen öffentlichen Root-Ordner ausbrechen kann. Wenn man das selbst baut, muss man solche und alle möglichen anderen Fälle berücksichtigen.
  • „Das Google VRP Team zahlte 500 Dollar als Belohnung für das Finden dieser Schwachstelle. Die Auswirkungen auf die Anwendung wurden nicht als schwerwiegend genug für eine höhere Belohnung eingeschätzt“ – offengelegte GCP-Konto-E-Mail-Adressen und private Schlüssel sind nur 500 Dollar wert? Was soll das. Typisch Google.

  • Zum Glück waren die geleakten Dinge weiterhin verschlüsselt. Wenn selbst ein Unternehmen, das auf diesen Bereich spezialisiert ist, nicht vor Leaks gefeit ist, ist das ehrlich gesagt wohl das Best-Case-Szenario.

  • Der Titel wurde ziemlich stark bearbeitet. Der Originaltitel lautet Hunting for Nginx Alias Traversals in the wild.
    Der HN-Einreichungstitel betont die Bitwarden-Schwachstelle, aber im Artikel wird auch ein Google-Fall behandelt.

    • Gut, ich habe den Titel zurückgesetzt. Der Einreichungstitel lautete „Leaking Bitwarden's Vault with a Nginx vulnerability“.
  • Was ist heutzutage die beste Option, wenn man nur zuverlässig und sicher statische Dateien ausliefern will, bei minimalem Ressourcenverbrauch? Früher hätte ich Nginx gewählt, aber aus Sicherheitssicht frage ich mich, ob ein stärker zweckgebundenes Tool mit weniger Konfigurationsmöglichkeiten besser wäre.

    • Ich verwende https://static-web-server.net/
      Es ist plattformübergreifend, in Rust geschrieben, hat eine einfache Konfiguration und sichere Defaults. Es gibt außerdem ein gehärtetes Container-Image und ein gehärtetes NixOS-Modul.
      Caddy würde ich nicht empfehlen. Das offizielle Docker-Image läuft standardmäßig als root [1] und es wird auch keine ordentlich gesandboxte systemd-Unit-Datei bereitgestellt [2].
      [1]: https://github.com/caddyserver/caddy-docker/issues/104
      [2]: https://github.com/caddyserver/dist/blob/master/init/caddy.s...
      Edit: Formulierung geglättet.
    • Wenn ich ein wenig Werbung machen darf: Caddy macht das hier sehr gut. Es bietet automatisches HTTPS, ist in Go geschrieben, sodass man sich keine Sorgen um Memory-Safety-Bugs machen muss, und hat ein solides file_server-Modul.
    • Ich nutze Caddy seit Jahren. Es bietet automatische SSL-Zertifikate, File Serving und Reverse Proxying, und die Konfiguration ist sehr einfach und klar. Als einzelnes Go-Binary ist auch die „Installation“ einfach, und es gibt nur eine Konfigurationsdatei.
    • Caddy ist ziemlich simpel zu konfigurieren, um statische Dateien auszuliefern.
    • Merecat: https://github.com/troglobit/merecat/
  • Vielleicht ist das eine dumme Frage, aber warum hat Bitwarden überhaupt nicht authentifizierte Requests auf /attachments erlaubt? Selbst mit dem Nginx-Bug wäre der Request doch fehlgeschlagen, wenn diese URL Authentifizierung erfordert hätte, oder?

    • Das ist ein Exploit gegen die Webserver-Konfiguration, daher wird weder Bitwardens Authentifizierungscode noch überhaupt Bitwarden-Code ausgeführt. Es ist weder seltsam noch falsch, dass das Projekt seine eigene Authentifizierung verwendet und nicht Nginx oder ein Modul.
      Trotzdem ist Bitwarden verantwortlich, weil sie über Docker eine gefährliche Konfiguration ausgeliefert haben. Bitwarden scheint das auch anzuerkennen und hat es danach behoben.
  • Entschuldigung für die dumme Frage. Würde so ein Traversal mit passenden Verzeichnis- und Dateibesitzrechten nicht verhindert?
    Wenn nginx nicht als root läuft, wie kann es dann andere Dateien lesen als die, die dem nginx-Benutzer ausdrücklich zugewiesen sind?

    • Natürlich kann man das verhindern. Die App unter einem Benutzer ausführen, nginx unter einem anderen, für alle App-Dateien go-rwx setzen, die Gruppe der „static“-Dateien auf www-data setzen und nur g+r vergeben – dann kann der Webserver nicht auf die App-Dateien zugreifen.
      Das ist im Wortsinne App-Hosting 101, und so haben es Leute schon vor 20 Jahren gemacht.
    • Ach, die Wunder der 022 umask. Persönlich empfehle ich immer, Dateien so anzulegen, dass andere Benutzer sie nicht lesen können. Wenn das für alle Dateien schwierig ist, dann zumindest für wichtige Verzeichnisse wie die unter /home.
      Man muss die Gruppenmitgliedschaften vielleicht etwas genauer anpassen, aber das ist es auf jeden Fall wert.
    • Ich weiß nicht, wie es anderen geht, aber heutzutage installiere ich nginx für private Zwecke nicht mehr richtig. Ich starte einfach ein Docker-Image. Und ich prüfe ehrlich gesagt nicht, ob das als root läuft oder nicht.
      Vielleicht mache ich da wirklich etwas gründlich falsch. Autsch.
    • Die übliche umask ist 022, daher können nginx-Worker die meisten Dateien lesen, aber nicht schreiben. Sie müssen nicht einmal ausdrücklich einem Benutzer wie www-data zugewiesen sein. Wenn eine Anwendung sensible Daten erzeugt, sollte man natürlich besser eine 077 umask verwenden.
    • Stimmt.
      Leider müssen nginx und andere Webserver bei typischen Webanwendungen normalerweise als root laufen, weil sie auf Port 80 oder 443 lauschen müssen. Ports unter 1024 können nur von root geöffnet werden.
      Eine ausführlichere Erklärung gibt es hier: https://unix.stackexchange.com/questions/134301/why-does-ngi...