1 Punkte von GN⁺ 2024-01-27 | 1 Kommentare | Auf WhatsApp teilen
  • Commit 0226b56 von 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.c in receive_http_response(), ob *buf_size < rx_message.BodyLength gilt, und behandelt den Fall bei Fehlschlag mit EFI_BAD_BUFFER_SIZE und Invalid Content-Length
  • Der Änderungsumfang betrifft 1 Datei, httpboot.c, mit 7 hinzugefügten und 1 gelöschten Zeile; außerdem wurde der Tippfehler Content-Lenght zu Content-Length korrigiert

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) in httpboot.c hinzu
  • Falls *buf_size == 0 ist, wird der Tippfehler in der bestehenden Fehlermeldung korrigiert und mit goto error verzweigt
    • Failed to get Content-LenghtFailed 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_SIZE gesetzt
    • Die Fehlermeldung Invalid Content-Length wird ausgegeben
    • Danach erfolgt ein Sprung mit goto error

Ä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.BodyLength größer als die allokierte Größe *buf_size ist

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

 
GN⁺ 2024-01-27
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.

    • Ich bin der Finder des Bugs aus dem Originalbeitrag; dass dieses Problem nur bei HTTP-Boot ausnutzbar sei, ist ein verbreitetes Missverständnis.
      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.cfg per 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.
    • Der Grund ist, dass Microsofts Rechtsabteilung offenbar der Ansicht ist: Wenn Microsoft GRUB, einen Bootloader unter GPLv3, signiert, könnte die GPLv3 Entwicklern das Recht geben, die Herausgabe der Signaturschlüssel zu verlangen.
      Quelle: https://techcommunity.microsoft.com/t5/hardware-dev-center/u...
    • Ich hatte mich gefragt, warum ein Bootloader Netzwerkkommunikation betreibt; mit der Erklärung, dass man ein EFI-Binary als HTTP-URL angeben kann, ergibt es Sinn.
    • Ich frage mich, wie shim das Problem vermeidet, vor dem Microsoft Sorge hat.
      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.
    • Wenn es darum geht, einen lokalen Bootloader der zweiten Stufe zu starten, könnte doch auch der Windows Boot Manager dieselbe Rolle übernehmen.
      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.BodyLength ein 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.BodyLength als 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.

    • Betrachtet man nur diesen Commit isoliert, kann man ihn leicht missverstehen.
      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.

    • Die Leute, die Secure Boot vorantreiben, sind von ähnlicher Sorte.
      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.
    • Gute Verteidigung besteht nur darin, wie bei Defense in Depth mehrere Verteidigungsschichten aufzubauen, und dieser Bug reißt ein Loch in eine davon.
    • Er könnte auch dazu genutzt werden, in einige gesperrte Geräte einzudringen.
    • Nach der hervorragenden Erklärung in einem anderen Thread https://news.ycombinator.com/item?id=39135275 kann man es als Critical sehen, weil der Angriffsvektor nicht nur auf HTTP beschränkt ist.
  • 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.

    • HTTPS ist hier irrelevant.
      Fehlerhafte Header können über beide Varianten gesendet werden.
    • Ich bin mir nicht sicher, ob HTTPS für diesen Zweck überhaupt möglich ist.
      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.
    • Das Problem hat nichts damit zu tun, ob ein S vorhanden ist oder nicht.
      Ich glaube, du hast das Problem nicht richtig verstanden.
  • Content-length ist nicht die tatsächliche Body-Länge, sondern die Länge nach Content-Encoding.

    • „HTTP/1.1 ist ein wunderbar einfaches Protokoll, wenn man das meiste davon ignoriert.“
  • 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.

    • Der HTTP-Client wird von UEFI als EFI-Treiber bereitgestellt.
      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.

    • Wenn shim eine Datei per HTTP oder verwandtem Protokoll abruft, versucht es, einen Puffer zum Speichern der empfangenen Daten zuzuweisen.
      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.
    • Der Beschreibung nach wird der Puffer anhand von Content-Length alloziert, aber es wird entsprechend der Größe des tatsächlich empfangenen Puffers kopiert, sodass über den allozierten Bereich hinaus geschrieben wird.