- Commit
0226b56von rhboot/shim behebt CVE-2023-40547, das dadurch entstand, dass beim Empfang einer Datei der Größenwert des HTTP-Headers ungeprüft vertraut wurde - Wenn ein manipulierter Header eine kleinere Größe als die tatsächlich empfangenen Daten angibt, kann shim einen kleineren Puffer als erforderlich reservieren
- Der bisherige Code verwendete den Header-Wert für die Allokation, für das Kopieren jedoch Protokoll-Metadaten, was zu einem out-of-bounds write führen konnte
- Der Patch prüft in
httpboot.cinreceive_http_response(), ob*buf_size < rx_message.BodyLengthgilt, und behandelt den Fall bei Fehlschlag mitEFI_BAD_BUFFER_SIZEundInvalid Content-Length - Der Änderungsumfang betrifft 1 Datei,
httpboot.c, mit 7 hinzugefügten und 1 gelöschten Zeile; außerdem wurde der TippfehlerContent-LenghtzuContent-Lengthkorrigiert
Ablauf der Entstehung der Schwachstelle
- CVE-2023-40547 ist ein Problem, das auftritt, wenn shim Dateien über HTTP oder ein verwandtes Protokoll abruft
- Beim Allokieren des Puffers zum Speichern der empfangenen Daten wurde der Größenwert des HTTP-Headers verwendet
- HTTP-Header können manipuliert werden und eine kleinere Größe als die tatsächlich empfangenen Daten angeben
- Im bisherigen Ablauf wurde der Header-Wert für die Pufferallokation verwendet, beim Kopieren der Daten aus dem rx-Puffer jedoch Protokoll-Metadaten als Grundlage genommen
- Durch diese Differenz konnten Daten kopiert werden, die größer als der allokierte Puffer waren; dadurch konnte ein out-of-bounds write entstehen
Inhalt des Patches
- Der Patch fügt eine Schutzprüfung in
receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size)inhttpboot.chinzu - Falls
*buf_size == 0ist, wird der Tippfehler in der bestehenden Fehlermeldung korrigiert und mitgoto errorverzweigtFailed to get Content-Lenght→Failed to get Content-Length
- Die neue Prüfung kontrolliert die Bedingung
*buf_size < rx_message.BodyLength- Wenn die Bedingung wahr ist, wird
efi_status = EFI_BAD_BUFFER_SIZEgesetzt - Die Fehlermeldung
Invalid Content-Lengthwird ausgegeben - Danach erfolgt ein Sprung mit
goto error
- Wenn die Bedingung wahr ist, wird
Änderungsumfang
- Geändert wurde eine Datei:
httpboot.c - Der Umfang der Änderung beträgt 7 hinzugefügte Zeilen und 1 gelöschte Zeile
- Entscheidend ist die Logik, die prüft, ob die Länge des empfangenen Bodys
rx_message.BodyLengthgrößer als die allokierte Größe*buf_sizeist
Zugehörige Aufzeichnungen
- Dieser Commit ist als Änderung zur Behebung von CVE-2023-40547 gekennzeichnet
- Die Commit-Nachricht hält ausdrücklich fest, dass das Problem durch fehlerhaftes Vertrauen in HTTP-Header verursacht wurde
- Als Melder der Schwachstelle ist Bill Demirkapi vom Microsoft Security Response Center verzeichnet
1 Kommentare
Hacker-News-Kommentare
shim ist ein EFI-Bootloader, der häufig von Linux-Distributionen verwendet wird, die Secure Boot aktivieren möchten.
Aus Sicht der Distributionen möchte man Secure Boot mit dem standardmäßig vorhandenen, von Microsoft signierten Schlüssel einfach aktivieren können, statt Nutzer ihre Schlüssel selbst registrieren zu lassen.
Da Microsoft jedoch in der Regel keine GPL-Bootloader wie GRUB signiert, wurde shim entwickelt, das mit dem Microsoft-Schlüssel signiert werden kann; shim prüft wiederum die Signatur des Boot-Ziels mit einem separaten Schlüssel namens Machine Owner Key, also MOK.
Wenn man shim das zu bootende EFI-Binary angibt, kann man eine HTTP-URL übergeben; ist der HTTP-Server dabei bösartig, kann das einen Out-of-bounds-Write auslösen.
Üblicherweise wird shim allerdings genutzt, um einen lokalen Bootloader der zweiten Stufe wie GRUB zu starten, daher dürfte das bei den meisten Installationen vermutlich kein Problem darstellen.
Secure Boot wurde von Anfang an so entworfen, dass auch bereits signierte Binaries über die DBX-Liste widerrufen werden können; wird diese Liste in UEFI eingespielt, wird das betreffende Binary trotz gültiger Signatur abgelehnt.
Wenn Signaturen alter shim-Binaries mit diesem Bug in die Liste aufgenommen werden, kann man die Liste auf den eigenen Geräten aktualisieren; sie kann auch über Capsule-Updates wie LVFS verteilt werden, und wer Secure-Boot-Schlüssel und -Variablen direkt verwaltet, kann die Liste unter https://uefi.org/revocationlistfile herunterladen und registrieren.
Sonst wäre es nicht als Critical eingestuft worden.
Der Bug kann ausgenutzt werden, wenn lokal privilegierte Malware die EFI-Partition überschreibt, bei einem Man-in-the-Middle-Angriff in einem benachbarten Netzwerk mit aktiviertem PXE-Boot sowie per Remote-Man-in-the-Middle-Angriff bei Nutzung von HTTP-Boot.
Ein unprivilegierter Remote-Angreifer in Man-in-the-Middle-Position kann ihn ohne direkten Zugriff ausnutzen, wenn das Opfergerät HTTP-Boot verwendet.
Ein Remote-Angreifer, der auf dem Opfergerät Berechtigungen und Codeausführung erlangt hat, kann Secure Boot auch dann umgehen, wenn das Opfer kein HTTP-Boot nutzt, sofern die Firmware HTTP unterstützt.
Beispielsweise kann er die Boot-Reihenfolge-Variablen so ändern, dass sie auf einen von ihm kontrollierten Server zeigen, oder den Bootloader auf der EFI-Partition mit legitimen shim- und GRUB2-Images überschreiben und dann in
grub.cfgper HTTP ein neues shim chainloaden lassen.Denn die Geräte-Syntax von GRUB2 kann unterstützte Geräte angeben, einschließlich HTTP.
Außerdem kann ein unprivilegierter Angreifer im Nachbarnetz in Man-in-the-Middle-Position den Angriff ausnutzen, wenn das Opfergerät PXE-Boot verwendet, indem er eine Kette wie PXE-shim → PXE-GRUB2 → HTTP-shim aufbaut.
Quelle: https://techcommunity.microsoft.com/t5/hardware-dev-center/u...
Wenn die Anti-Tivoization-Klausel der GPLv3 verlangen kann, Secure-Boot-Signaturschlüssel bereitzustellen, müsste dann nicht auch der MOK-Signaturschlüssel auf Anfrage bereitgestellt werden?
Dann würde im Grunde jeder einen Schlüssel bekommen, mit dem sich beliebiger Code signieren lässt, der indirekt über Secure Boot gestartet wird; ich weiß nicht, ob sich das sinnvoll davon unterscheidet, dass Microsoft einfach Signaturschlüssel an GPLv3-Projekte wie GRUB ausgibt.
Auf alten BIOS-Systemen hatte ich WBM schon einmal so eingerichtet, dass er GRUB chainloadet; auf UEFI-Systemen habe ich das noch nicht ausprobiert, daher weiß ich nicht, ob es dort Hürden gibt.
Man könnte sich fragen: „Warum von einem nicht vertrauenswürdigen oder kompromittierten Server booten?“ oder „Wenn der Server kompromittiert ist, kann er doch einfach ein bösartiges Binary ausliefern, ist das also nicht bedeutungslos?“ Kurz gesagt: Das Binary, das shim letztlich bootet, muss mit MOK signiert sein.
Daher sollten unabhängig davon, ob man in einem kompromittierten Netzwerk bootet, per HTTP bootet oder von einem kompromittierten Server bootet, und unabhängig von HTTPS, dieselben Sicherheitsgarantien gelten.
Secure Boot verhindert keine Downgrades; dass ein kompromittierter Server für Downgrade-Angriffe genutzt werden kann, ist daher von dieser Schwachstelle getrennt zu betrachten.
Schutz vor Downgrade-Angriffen muss ohnehin separat und robuster implementiert werden.
Allerdings weiß ich nicht, warum shim HTTP-Boot direkt unterstützen muss.
Das könnte doch auch ein zweites lokales, mit MOK signiertes EFI-Binary übernehmen; vermutlich hielt man es für eine vergleichsweise einfach zu implementierende Funktion.
Ich verstehe nicht, warum dieser Code die Länge des Bodys nach zwei verschiedenen Kriterien behandelt.
Nach RFC ist Content-Length in HTTP/1.1 die maßgebliche Information über die Länge des Bodys einer HTTP-Anfrage/-Antwort.
Daten auf der Leitung, die über diese Länge hinausgehen, sind per Definition Teil einer anderen Nachricht.
Ist Content-Length dagegen größer als
rx_message.BodyLength, bedeutet das, dass die vollständige Nachricht noch nicht empfangen wurde; dann sollte man weiter warten oder einen Timeout-Fehler ausgeben.In jedem Fall ist
rx_message.BodyLengthein falscher Wert, wenn nicht garantiert ist, dass er Content-Length entspricht.Wenn man großzügiger sein will, gibt es keinen Grund, den Content-Length-Header anzusehen; man kann einfach
rx_message.BodyLengthals Puffergröße nehmen und alle Daten auf der Leitung als empfangene Nachricht interpretieren.Der aktuelle Code ist unnötig kompliziert, und genau so schleichen sich Bugs ein.
Im umliegenden Code https://github.com/rhboot/shim/blob/0226b56513b2b8bd5fd281bc... sieht man, dass in einer Schleife Datenstücke empfangen werden und jedes Mal geprüft wird, ob neue Daten die durch Content-Length festgelegte Pufferkapazität überschreiten.
Zuvor wurde diese Prüfung aber nicht für den ersten Lesevorgang außerhalb der Schleife durchgeführt, und das war der Bug.
Allerdings sehe ich keinen Code, der am Ende prüft, ob die heruntergeladene Größe
*buf_size, also Content-Length, entspricht.Wenn diese Bedingung verletzt ist, könnte das ein Hinweis darauf sein, dass die Verbindung zu früh geschlossen wurde.
Das ist eindeutig ein Bug, und es ist gut, dass er behoben wurde, aber ich frage mich, wer sein eigenes Gerät von einem nicht vertrauenswürdigen Host bootet.
Wenn ein Angreifer den HTTP-Dienst so weit kontrolliert, dass er bösartige Header senden kann, ist das Vermeiden dieses Overflows das kleinste Problem; auch das Zertifikat wäre kompromittiert, und er könnte Malware in einem spezifikationskonformen Payload ausliefern.
Ein Bug ist es schon, aber ob er Critical ist, da bin ich mir nicht sicher.
Sie glauben ernsthaft an eine Sicherheitsstrategie, bei der alles, was potenziell kompromittiert werden kann, nicht mit Secure Boot signiert sein darf.
Wenn es auch nur irgendetwas Signiertes mit einer Schwachstelle gibt, kann man es nutzen, um die Secure-Boot+TPM-verschlüsselten Festplatten aller zu entschlüsseln.
Ich finde schwer nachvollziehbar, warum dieser Ansatz als gültiges Sicherheitsmodell angesehen wurde, und solche Schwachstellen gibt es bereits zuhauf.
Außerdem wird der Elefant im Raum, Windows, komplett ignoriert.
Ein Beispiel für diese Denkweise: https://lkml.org/lkml/2018/4/3/767
Trotz Linus’ Bedenken aktivieren viele Distributionen beim Booten mit Secure Boot tatsächlich einen Integritätsmodus.
Das liegt wahrscheinlich an Microsofts Richtlinien und daran, dass Distributionen, um Microsofts UEFI-Signatur zu bekommen, gezwungen sind, dem in diesem Thread beschriebenen Verfahren zu folgen.
Das Ergebnis ist, dass Secure Boot bei normalen Distributionen oft Funktionen einschränkt, zum Beispiel den Ruhezustand unmöglich macht.
Wenn wichtige Dinge passieren, sollte man das S in HTTP verwenden.
Das Booten von Geräten gehört dazu, und HTTPS-Header waren schon immer verschlüsselt.
Trotzdem ein gut gefundener Bug.
Fehlerhafte Header können über beide Varianten gesendet werden.
Für die Verschlüsselung braucht man nämlich eine korrekte Uhrzeit und ein korrektes Datum.
Die RTC könnte gültig sein, aber ich weiß nicht, ob Zeitzonen sauber behandelt werden, und ohnehin könnte die Uhr falsch gehen.
Ich glaube, du hast das Problem nicht richtig verstanden.
Content-lengthist nicht die tatsächliche Body-Länge, sondern die Länge nach Content-Encoding.Enthalten diese shim-Builds überhaupt httpboot?
Soweit ich weiß, ist shim nur dazu da, andere EFI-Binaries auf der Festplatte auszuführen, und ich glaube nicht, dass ich die Netzwerk-Boot-Funktion von shim je tatsächlich im Einsatz gesehen habe.
Vielleicht übersehe ich etwas, aber ich dachte, die meisten HTTP-Clients lesen nur bis zur angegebenen Content-Length, und wenn weniger Bytes gelesen werden als Content-Length, gilt das als Fehler.
Aus meiner Sicht legt die UEFI-Spezifikation nicht konkret fest, wie man sich verhalten soll, wenn Content-Length-Header und Länge des Antwort-Bodys nicht übereinstimmen.
Daher ist es gut möglich, dass manche Implementierungen einfach eine
connection:close-Anfrage erzeugen und Content-Length nicht prüfen.Diese Schwachstelle wurde von MSRC gemeldet, und die CVE-Beschreibung enthält nichts über eine tatsächliche Ausnutzung.
Das könnte später noch offengelegt werden, oder es könnte ein theoretisches Problem sein.
Kann jemand erklären, warum es gefährlich ist, weniger als die tatsächliche Body-Länge zu lesen?
Ich hätte erwartet, dass eher das Gegenteil gefährlich ist.
Die Größe wird jedoch aus einem manipulierbaren HTTP-Header entnommen, und ein Angreifer kann eine kleinere Größe als die der empfangenen Daten angeben.
In diesem Fall verwendet der Code für die Allokation den Header-Wert, beim Kopieren aus dem Empfangspuffer aber die Größe aus den Protokoll-Metadaten, wodurch es zu einem Out-of-bounds Write kommt.