1 Punkte von GN⁺ 2024-07-02 | 1 Kommentare | Auf WhatsApp teilen
  • Aufgrund einer Race Condition im Signal-Handler des OpenSSH-Servers sshd kann ein nicht authentifizierter Client innerhalb der Authentifizierungsfrist LoginGraceTime auf Servern mit Standardkonfiguration Remote Code Execution auslösen
  • Diese Schwachstelle ist eine Regression von CVE-2006-5051 und trat abermals in Versionen ab 8.5p1 bis vor 9.8p1 auf, nachdem ein OpenSSH-8.5p1-Commit im Oktober 2020 die Schutzmaßnahme in sigdie() entfernt hatte
  • Auf glibc-basierten Linux-Systemen ruft syslog() async-signal-unsafe Funktionen wie malloc() und free() auf, was im nicht sandboxed privilegierten Code von sshd zu unauthentifizierter Root-RCE führen kann
  • Die Tests wurden auf einer i386-VM und in einem stabilen Netzwerk mit etwa 10 ms Paket-Jitter durchgeführt; auf Debian 12.5.0 mit OpenSSH 9.2p1 dauerte es bei durchschnittlich etwa 10.000 Versuchen sowie MaxStartups=100 und LoginGraceTime=120 ungefähr 6 bis 8 Stunden bis zur Root-Shell
  • OpenSSH hat das Problem am 6. Juni 2024 mit Commit 81c1099 behoben; wenn ein Update oder eine Neukompilierung schwierig ist, kann LoginGraceTime 0 RCE verhindern, allerdings bleibt dann das Risiko einer DoS durch Erschöpfung der MaxStartups-Verbindungen bestehen

Wo die Schwachstelle entsteht

  • Das Problem in OpenSSH sshd beginnt im SIGALRM-Handler, der vor der Authentifizierung aktiv ist
    • Wenn ein Client sich nicht innerhalb von LoginGraceTime authentifiziert, wird der SIGALRM-Handler asynchron aufgerufen
    • Dieser Handler ruft Funktionen wie syslog() auf, die nicht async-signal-safe sind
    • Der Standardwert ist LoginGraceTime=120 Sekunden; in älteren OpenSSH-Versionen waren es 600 Sekunden
  • Die Schwachstelle ist eine Regression von CVE-2006-5051
    • CVE-2006-5051 ist eine 2006 von Mark Dowd gemeldete Race Condition im Signal-Handler von OpenSSH-Versionen vor 4.4
    • Ein Commit 752250c in OpenSSH 8.5p1 entfernte im Oktober 2020 versehentlich #ifdef DO_LOG_SAFE_IN_SIGHAND aus sigdie()
  • Der betroffene Versionsbereich ist klar abgegrenzt
    • OpenSSH vor 4.4p1: verwundbar, falls Patches zu CVE-2006-5051 oder CVE-2008-4109 nicht zurückportiert wurden
    • OpenSSH ab 4.4p1 bis vor 8.5p1: für diese Race Condition nicht verwundbar, da sigdie() auf den sicheren Aufruf _exit(1) umgestellt wurde
    • OpenSSH ab 8.5p1 bis vor 9.8p1: durch Entfernung der Schutzmaßnahme erneut verwundbar

Betroffene Umgebungen und Ausnahmen

  • Ziel für Remote-Ausnutzung ist glibc-basiertes Linux
    • syslog() in glibc ruft intern async-signal-unsafe Funktionen wie malloc() und free() auf
    • Der verwundbare Code befindet sich im privilegierten Teil von sshd und läuft ohne Sandbox mit vollen Rechten
    • Dadurch wird unauthentifizierte Remote-Code-Ausführung als root möglich
  • Andere libc-Implementierungen oder Betriebssysteme wurden nicht untersucht
  • OpenBSD ist nicht verwundbar
    • Der SIGALRM-Handler von OpenBSD ruft statt syslog() die Funktion syslog_r() auf
    • syslog_r() ist eine von OpenBSD im Jahr 2001 eingeführte async-signal-sicherere Variante

Voraussetzungen der Remote-Ausnutzungsforschung

  • Um diese Race Condition remote auszunutzen, mussten drei Probleme gelöst werden
    • Es wird ein Codepfad benötigt, der sshd in einem inkonsistenten Zustand hinterlässt, wenn SIGALRM zum richtigen Zeitpunkt dazwischenfunkt
    • Dieser Codepfad muss erreichbar sein, und die Wahrscheinlichkeit einer Unterbrechung zum richtigen Zeitpunkt muss erhöht werden
    • Das Timing muss auch über ein entferntes Netzwerk hinweg getroffen werden können
  • Die Forschung begann nicht direkt mit aktuellen Schutzmechanismen, sondern zunächst in einer älteren i386-OpenSSH-Umgebung und wurde dann auf neuere Versionen ausgeweitet
  • Die Testbedingungen hatten klare Grenzen
    • Es wurden nur virtuelle Maschinen getestet, keine Bare-Metal-Server
    • Das Netzwerk war ein relativ stabiler Link mit etwa 10 ms Paket-Jitter
    • Mehrere Teile des Exploits lassen sich noch verbessern
    • Arbeiten an einem amd64-Exploit haben begonnen, sind wegen stärkerem ASLR jedoch deutlich schwieriger

Experimente mit älteren OpenSSH-Zielen

  • Debian 3.0r6, OpenSSH 3.4p1

    • Das Ziel war SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3, also eine Debian-3.0r6-Umgebung aus dem Jahr 2005
    • Diese Debian-Version war die erste mit standardmäßig aktivierter Privilege Separation, und wichtige damalige Sicherheits-Patches waren bereits eingespielt
    • Die Ausnutzung nutzte die Unterbrechung von free() und einen inkonsistenten Heap-Zustand
      • Ein Aufruf von free() im Public-Key-Parsing-Code wurde durch SIGALRM unterbrochen
      • Anschließend wurde der inkonsistente Heap-Zustand bei einem weiteren free() in packet_close() ausgenutzt
    • glibc 2.2.5 enthielt noch keine Härtung gegen Solar Designers unlink()-Technik
    • Der Angriff überschreibt __free_hook und lenkt den Kontrollfluss auf die Adresse von Shellcode im Heap um
    • Diese Debian-Version hatte weder ASLR noch NX
    • Nach Timing-Optimierungen waren im Mittel etwa 10.000 Versuche nötig
    • Mit MaxStartups=10 und LoginGraceTime=600 dauerte es im Durchschnitt etwa eine Woche bis zur Remote-Root-Shell
  • Ubuntu 6.06.1, OpenSSH 4.2p1

    • Das Ziel war SSH-2.0-OpenSSH_4.2p1 Debian-7ubuntu3, also eine Ubuntu-6.06.1-Umgebung aus dem Jahr 2006
    • Es war die letzte Ubuntu-Version, die noch für CVE-2006-5051 verwundbar war
    • glibc 2.3.6 nimmt beim Eintritt in die malloc-Funktionsfamilie einen Mandatory Lock; daher führt das Unterbrechen eines malloc() und anschließende Ausnutzen eines weiteren malloc() zu einem Deadlock
    • Der endgültige Ausnutzungspfad nutzte PAM
      • pam_start() setzt den globalen sshpam_handle-Zeiger von sshd
      • Wenn _pam_add_handler() unterbrochen wird, kann ein nicht initialisiertes next-Feld zurückbleiben
      • Wenn pam_end() im SIGALRM-Handler aufgerufen wird, kann ein beliebiger Zeiger an free() übergeben werden
    • Da die alte unlink()-Technik in glibc blockiert war, wurde die Fastbin-Variante von House of Mind aus Malloc Maleficarum verwendet
    • Eine Fake-Arena wurde auf .got.plt von sshd ausgerichtet, und der _exit()-Eintrag wurde mit der Adresse von Heap-Shellcode überschrieben
    • Der Heap dieses Ubuntu war standardmäßig ausführbar
    • Im Durchschnitt waren etwa 10.000 Versuche erforderlich
    • Mit MaxStartups=10 und LoginGraceTime=120 dauerte es im Mittel etwa 1 bis 2 Tage bis zur Remote-Root-Shell
    • Bei Pech kann ein Angreifer alle 10 MaxStartups-Verbindungen in einen Deadlock bringen, bevor eine Root-Shell erreicht wird

Debian 12.5.0, OpenSSH 9.2p1

  • syslog()- und glibc-malloc-Pfad

    • Das Ziel war SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2, also die 2024 aktuelle Stable-Umgebung von Debian 12.5.0
    • Diese Umgebung ist für die Regression von CVE-2006-5051 verwundbar
    • Der SIGALRM-Handler dieser Version ruft weder packet_close() noch pam_end() auf, sondern läuft über den syslog()-Pfad
      • grace_alarm_handler() ruft sigdie() auf
      • sigdie() ruft über sshlogv() und do_log() schließlich syslog() auf
    • syslog() in Debian glibc 2.36 ruft beim ersten Aufruf malloc() auf
      • Über __localtime64_r() wird __tzfile_read() aufgerufen
      • fopen() ruft malloc(304) für eine FILE-Struktur auf
      • Für den internen Read-Buffer wird außerdem malloc(4096) aufgerufen
    • Seit Oktober 2017 setzt glibc malloc in Single-Thread-Szenarien keinen Mandatory Lock mehr
    • In Single-Thread-Prozessen wie sshd entsteht dadurch wieder eine Möglichkeit, malloc-Konkurrenz auszunutzen
  • ASLR-Bedingungen und i386-Einschränkungen

    • Die Debian-12.5.0-i386-Umgebung weist eine ASLR-Schwäche auf
    • PIE von sshd, Heap, die meisten Bibliotheken und der Stack werden normalerweise randomisiert
    • glibc selbst wird jedoch immer bei 0xb7200000 oder 0xb7400000 gemappt
    • Die glibc-Adresse lässt sich damit mit 50-prozentiger Wahrscheinlichkeit erraten
    • Der Exploit geht davon aus, dass glibc bei 0xb7400000 gemappt ist
    • Diese Adresse trat etwas häufiger auf als 0xb7200000
  • Heap-Inkonsistenz und Ausnutzung der FILE-Struktur

    • Der gewählte malloc-Pfad ist der Split-Pfad, bei dem ein großer Free-Chunk in zwei Teile geteilt wird
    • Dabei entstehen ein zurückgegebener Chunk und ein Remainder-Chunk
    • Wenn SIGALRM genau dann dazwischenfunkt, nachdem der Remainder-Chunk in die Unsorted List verkettet wurde, aber bevor sein Größenfeld initialisiert wurde, entsteht eine Heap-Inkonsistenz
    • Der Angreifer kontrolliert das Größenfeld des Remainder-Chunks über Restdaten einer vorherigen Heap-Allokation
    • Dadurch kann der Remainder-Chunk größer erscheinen als er tatsächlich ist und sich mit anderen Heap-Chunks überlappen
    • Wenn malloc im SIGALRM-Handler diesen Chunk nutzt, wird Heap-Speicher korrumpiert
    • Ziel ist die FILE-Struktur, die fopen() in __tzfile_read() auf dem Heap allokiert
      • Mit der begrenzten Heap-Korruption wird 1 Byte des _vtable_offset der FILE-Struktur überschrieben
      • Dadurch sucht eine glibc-libio-Funktion den Vtable-Zeiger nicht an der Standardposition, sondern an einem anderen Offset
      • Den dort liegenden Fake-Vtable-Zeiger sowie den _codecvt-Zeiger kontrolliert der Angreifer über Restdaten früherer Heap-Allokationen
    • In i386-glibc kann diese Technik dazu führen, dass in __fread_unlocked() ein beliebiger Funktionszeiger __fct aufgerufen wird
    • amd64-glibc scheint _vtable_offset nicht zu verwenden
  • Heap-Layout und 27 Race Windows

    • Da ein einzelnes kleines Race Window kaum ausreicht, werden 27 Paare aus großem und kleinem Hole erzeugt
    • 28 Paare würden PACKET_MAX_SIZE von 256 KB überschreiten
    • Das finale Paket erzwingt die Reihenfolge malloc(~4KB), malloc(304), malloc(~4KB), malloc(304)
    • Wenn die Aufteilung eines großen Hole im richtigen Moment unterbrochen wird, allokiert fopen() im SIGALRM-Handler das zugehörige kleine Hole als FILE-Struktur
    • Für den Aufbau des Heap-Layouts wird der Public-Key-Parsing-Code von sshd genutzt
      • Über die Pfade cert_parse() und cert_free() werden nahezu beliebige Sequenzen von malloc() und free() ausgeführt
      • Da kein Memory Leak gefunden wurde, werden tcache-Chunks vorübergehend als Barrier Chunks genutzt
    • Es werden fünf Typen von Public-Key-Paketen gesendet
      • a/ unkontrollierbare Heap-Allokationen werden in tcache-Chunks gelenkt
      • b/ 27 Paare aus großem/kleinem Hole sowie Barrier Chunks werden erzeugt
      • c/ Fake-Header, Fake-Footer, Fake-Vtable und _codecvt-Zeiger werden geschrieben
      • d/ ein großer String von fast 256 KB wird allokiert und freigegeben, um die Holes aus der Unsorted List in die jeweiligen malloc-Bins zu verschieben
      • e/ die letzte malloc-Sequenz wird erzwungen, um 27 kleine Race Windows zu öffnen
  • Timing-Strategie für aktuelle Versionen

    • Die feedbackbasierte Timing-Strategie aus älteren Versionen funktionierte gegen OpenSSH 9.2p1 nicht
    • Das fünfte Public-Key-Parsing dauerte etwa 10 ms, wodurch das große Race Window zu groß wurde
    • user_specific_delay(), eingeführt in OpenSSH 7.8p1, verzögert Antworten um bis zu etwa 9 ms und zerstörte damit das bisherige Feedback-Verfahren
    • Die neue Strategie vergleicht Antwortzeiten von zwei Arten absichtlich ausgelöster Fehler
      • Ein Paket wird gesendet, das direkt vor dem Public-Key-Parsing einen Fehler auslöst
      • Ein weiteres Paket wird gesendet, das direkt nach dem Public-Key-Parsing einen Fehler auslöst
      • Aus der Differenz der Antwortzeiten wird die Dauer des letzten Public-Key-Parsings geschätzt
    • Mit dieser Strategie wurde die Race Condition im Mittel nach etwa 10.000 Versuchen gewonnen
    • Mit MaxStartups=100 und LoginGraceTime=120 dauerte es im Durchschnitt etwa 3 bis 4 Stunden, die Race Condition zu gewinnen
    • Wegen ASLR waren bis zur Remote-Root-Shell im Mittel etwa 6 bis 8 Stunden nötig

Fortschritt beim amd64-Exploit

  • Als amd64-Ziel wurde Rocky Linux 9 gewählt
    • Das Ziel-Image war Rocky-9.4-x86_64-minimal.iso
    • OpenSSH 8.7p1 ist für diese Race Condition im Signal-Handler verwundbar
    • Wegen einer ASLR-Schwäche wird glibc auf Vielfachen von 2 MB gemappt, wodurch Partial Pointer Overwrite leistungsfähiger wird
  • syslog() in glibc 2.34 von Rocky Linux 9 ruft intern __open_memstream() auf
    • Dabei wird eine FILE-Struktur auf dem Heap per malloc() angelegt
    • Außerdem werden calloc(), realloc() und free() aufgerufen, was zusätzliche Möglichkeiten eröffnet
  • Auf Basis der Heap-Korruptionsprimitive, zweier auf dem Heap allokierter FILE-Strukturen und 21 fixer Bits der glibc-Adresse wird eine Ausnutzbarkeit auch auf amd64 für möglich gehalten
    • Die erwartete Dauer soll länger sein als die 6 bis 8 Stunden auf i386, aber unter einer Woche bleiben
  • Es gibt auch eine separate Beobachtung zu Ubuntu 24.04
    • Ubuntu 24.04 randomisiert ASLR für sshd-Child-Prozesse nicht neu, sondern nur einmal beim Booten
    • Als Ursache wurde systemd-socket-activation.patch identifiziert, das rexec_flag deaktiviert
    • Das ist generell keine gute Entscheidung, verhindert hier aber die Ausnutzung, weil syslog() im SIGALRM-Handler dann nicht der erste syslog()-Aufruf ist und deshalb keine malloc-Funktionen aufruft
    • Zugehöriger Patch: https://git.launchpad.net/ubuntu/+source/…

Patch und Gegenmaßnahmen

  • OpenSSH hat diese Race Condition am 6. Juni 2024 mit Commit 81c1099 behoben
    • 81c1099: sshd(8) erhält eine Funktion, die problematisches Client-Verhalten sanktioniert
    • Async-signal-unsafe Code wurde aus dem SIGALRM-Handler von sshd in den Listener-Prozess verlagert und dort synchron verarbeitet
  • Diese Korrektur basiert auf dem großen Commit 81c1099 sowie dem noch größeren Defense-in-Depth-Commit 03e3de4, was Backports erschweren kann
  • Wenn ein Backport schwierig ist, kann man den async-signal-unsafe Code in sshsigdie() entfernen oder auskommentieren, sodass nur noch _exit(1) aufgerufen wird
  • Falls weder Update noch Neukompilierung möglich sind, kann in der Konfiguration LoginGraceTime auf 0 gesetzt werden
    • Das verhindert die Remote-Code-Ausführung aus diesem Advisory
    • Dafür bleibt eine DoS-Anfälligkeit durch Erschöpfung aller MaxStartups-Verbindungen bestehen

Offenlegungszeitplan

  • 2024-05-19: Kontaktaufnahme mit den OpenSSH-Entwicklern; danach mehrere Runden aus Patches und Reviews
  • 2024-06-20: Kontaktaufnahme mit distros@openwall
  • 2024-07-01: Veröffentlichung zum koordinierten Release-Datum

1 Kommentare

 
GN⁺ 2024-07-02
Meinungen auf Hacker News
  • Interessanterweise sah es so aus, als sei der RCE-Fix schon vor fast einem Monat öffentlich „untergemischt“ worden.
    Wenn PerSourcePenalties aktiviert ist, überwacht sshd(8) den Exit-Status von Child-Session-Prozessen vor der Authentifizierung und vermerkt Bedingungen wie wiederholte Authentifizierungsfehler oder sshd-Crashes für eine bestimmte Zeit als Penalty für die Client-Adresse.
    https://github.com/openssh/openssh-portable/commit/81c1099d2...
    Das wirkt ziemlich clever: weniger wie ein Patch, der sich rückwärts analysieren lässt und einem Angreifer etwas verrät, sondern eher wie eine Änderung an der Binary-Struktur, die die konkrete Schwachstelle beseitigt und als Nebeneffekt auch die gesamte Exploit-Familie abschwächt.

    • Das ist nicht der RCE-Fix; der eigentliche RCE-Fix ist dieser hier: https://news.ycombinator.com/item?id=40843865
      Die obige Änderung ist ein bereits angekündigtes Feature, um Müll-Verbindungen zu behandeln, und erschwert lediglich das Gewinnen der Race Condition, wodurch auch diese Schwachstelle abgeschwächt wird.
      Frühere Diskussion: https://news.ycombinator.com/item?id=40610621
    • Ich frage mich, ob dieser Fix schon in Distributionen übernommen oder von ihnen eingepflegt wurde.
    • Interessant ist, dass dieser Kommentar 2 Tage lang ganz oben blieb, obwohl er falsch ist und direkt darunter korrigiert wurde.
      Ich frage mich, ob Leute nur den ersten Kommentar im Thread lesen, ihn upvoten und dann mit einem falschen Eindruck weiterziehen.
  • Eine Passage in den OpenSSH-Release-Notes ist interessant:
    „Eine erfolgreiche Ausnutzung wurde auf 32-Bit-Linux/glibc-Systemen mit aktiviertem ASLR demonstriert. Unter Laborbedingungen muss der Angriff im Durchschnitt 6 bis 8 Stunden lang kontinuierlich Verbindungen bis zum vom Server erlaubten Maximum aufrechterhalten. Es wird angenommen, dass dies auch auf 64-Bit-Systemen möglich ist, wurde aber noch nicht demonstriert. Solche Angriffe werden sich wahrscheinlich verbessern.“
    https://www.openssh.com/releasenotes.html

  • Wenn man sich das Diff [1] ansieht, mit dem der Bug eingeführt wurde, scheint das Problem der Analyse zufolge darin zu liegen, dass sigdie() von einer Form, die mit #ifdef DO_LOG_SAFE_IN_SIGHAND umschlossen war, zu sshsigdie() refaktoriert wurde, das direkt sshlogv() aufruft, wobei das #ifdef wegfiel.
    Was hätte das verhindern können? Hätte es mehr Pull-Request-Review geben müssen? Es ist erstaunlich, dass Software, auf die sich die ganze Welt für sichere Verbindungen verlässt, offenbar faktisch von zwei Leuten [2] gewartet wird.
    [1] https://github.com/openssh/openssh-portable/commit/752250caa...
    [2] https://github.com/openssh/openssh-portable/graphs/contribut...

    • Im Nachhinein ist es leicht zu sagen, was man hätte tun können, um es zu verhindern.
      In diesem Fall hätte ein Kommentar dazu helfen können, warum das #ifdef nötig ist. Zum Beispiel etwa: „Der Code hier muss async-signal-safe sein, und der Lock-Zustand kann unbestimmt sein.“
      Allerdings steht getrlimit ehrlich gesagt auch nicht auf dieser Liste: https://man7.org/linux/man-pages/man7/signal-safety.7.html
      Trotzdem wäre es bei einem Review möglicherweise aufgefallen, wenn Code mit einem Kommentar zur async-signal-safety entfernt oder geändert worden wäre. Im zitierten Code deutet nur etwas wie SAFE_IN_SIGHAND darauf hin, dass dieser Code innerhalb eines Signal-Handlers sicher sein muss.
    • Da OpenBSD das System so refaktoriert hat, dass es eine async-signal-safe, reentrante syslog-Funktion verwendet, hat der Autor dieses Codes möglicherweise einfach angenommen, dass die Änderung sicher sei.
      Vielleicht hat er vergessen oder nicht gewusst, dass auf anderen Plattformen, von denen die OpenBSD-ssh-Entwickler nicht wirklich behaupten, sie zu unterstützen, weiterhin Funktionen verwendet werden, die nicht async-signal-safe sind.
    • Es ist Open Source. Wenn du meinst, du kannst es besser machen, kannst du jederzeit forken.
      Man hat keinen Anspruch darauf, von Open-Source-Entwicklern etwas zu bekommen. Auch sie können Fehler machen, und sie können selbst entscheiden, wie viele Maintainer oder Reviewer sie haben wollen.
      https://gist.github.com/richhickey/1563cddea1002958f96e7ba95...
    • Bei „Software, auf die sich die ganze Welt für sichere Verbindungen verlässt, wird faktisch von zwei Leuten gewartet“ muss man obligatorisch an diesen xkcd denken: https://xkcd.com/2347/
    • Es gab einige Möglichkeiten, das zu verhindern:
      1. Eine vernünftige Programmiersprache verwenden, die es nicht erlaubt, beliebige Funktionen als Signal-Handler zu setzen. In normaler libc ist das offensichtlich unsicher; in sicheren Sprachen wie Rust oder Java kann man so etwas nicht auf diese Weise tun.
      2. Eine gut implementierte libc verwenden, bei der der Aufruf von Funktionen, die nicht async-signal-safe sind, höchstens zu einem Deadlock statt zu Speicherbeschädigung führt. Wenn man Code, der innerhalb eines Signals ausgeführt wird, im Hinblick auf den Zugriff auf Thread Local Storage wie einen separaten Thread behandelt, ist das relativ einfach möglich; und wenn es keine globalen Mutexes gibt oder man den unterbrochenen Code, der einen Mutex hält, wieder fortsetzen kann, lässt sich auch ein Deadlock vermeiden.
      3. Beim Ändern und Freigeben von Code nachdenken. Nicht so handeln wie die Leute, die wie in [1] ohne Begründung das #ifdef entfernt haben.
      4. Statt OpenSSH einfache, gut designte Software verwenden, die von guten Programmierern geschrieben wurde.
  • Auch die Release Notes sind lesenswert: https://www.openssh.com/releasenotes.html
    Das ist eigentlich eine Variante eines interessanten Signal-Race-Condition-Bugs. Dem Schwachstellenbericht zufolge ist „OpenBSD insbesondere nicht anfällig, weil der SIGALRM-Handler syslog_r() aufruft, eine von OpenBSD 2001 erstellte, für asynchrone Signale sicherere Version von syslog()
    Mit anderen Worten: Die Signal-Safety-Abmilderung hat die OpenBSD-Entwickler dazu gebracht, nichttrivialen Code in den Signal-Handler zu packen, und als dieser Code auf andere Systeme portiert wurde, wurde er unsicher. Hätte man, entsprechend der üblichen Weisheit und Unix-Code-Konvention, den Code im Signal-Handler auf ein Minimum refaktoriert, wäre dieser Bug vermieden worden

    • Theo de Raadt hat eine ziemlich plausible Beobachtung zur Vermeidung dieses und ähnlicher Bugs gemacht: Kein Signal-Handler sollte Funktionen aufrufen, die keine signal-sicheren Systemaufrufe sind
      Denn mit der Zeit ist es allzu leicht, dass sich irgendwo in transitiven Aufrufen ein nicht für asynchrone Signale sicherer Aufruf einschleicht, und es ist auch nicht immer offensichtlich, dass dieser Pfad aus einem Signal-Kontext erreichbar ist
    • Es ist gut möglich, dass unter den jungen Systemadministratoren oder Praktikanten, die diese Schwachstelle patchen müssen, einige noch gar nicht geboren waren, als OpenBSD diese Lösung implementiert hat
  • Nachdem ich meine OpenSSH-Instanzen aktualisiert hatte, habe ich, da sie gegen musl und nicht glibc gelinkt sind, nachgesehen, ob musls syslog(3) ebenfalls Allokationen vornimmt und damit auf dieselbe Weise leicht ausnutzbar ist
    Sieht nicht so aus: https://github.com/bminor/musl/blob/master/src/misc/syslog.c
    Alles dort sind entweder Stack-Variablen oder statische Variablen, bei denen ein Lock Reentrancy verhindert. Die {d,sn,vsn}printf()-Aufrufe allozieren in musl ebenfalls nicht, könnten es aber in glibc tun. Übersehe ich etwas?

    • Bestätigung von Rich: https://fosstodon.org/@musl/112711796005712271
    • Wenn die Einschätzung zu den Allokationen stimmt, wäre der schlimmste Fall wohl nur ein Deadlock, weil der Lock nicht rekursiv ist
      Ein Deadlock in sigalrm könnte dennoch das Aufräumen von Verbindungen verhindern und so zu einem Denial of Service führen
  • Ein Patch für FreeBSD ist erschienen
    Ob es betroffen ist, ist unklar. Die bekannten Exploits waren nur unter glibc möglich, und FreeBSD nutzt kein glibc, aber es ist besser, auf Nummer sicher zu gehen
    https://www.freebsd.org/security/advisories/FreeBSD-SA-24:04...

  • Dem Bericht zufolge kann man, wenn man sshd nicht aktualisieren oder neu kompilieren kann, diese Signal-Handler-Race-Condition allein dadurch beheben, dass man in der Konfigurationsdatei LoginGraceTime auf 0 setzt
    In diesem Fall wird sshd zwar anfällig für einen Denial of Service, bei dem alle MaxStartups-Verbindungen ausgeschöpft werden, ist aber vor der in dieser Empfehlung beschriebenen Remote Code Execution sicher
    Es scheint also eine Abmilderung zu sein, LoginGraceTime 0 in sshd_config zu setzen

    • Moment, unter https://www.man7.org/linux/man-pages/man5/sshd_config.5.html steht, dass ein Wert von 0 kein Zeitlimit bedeutet
      Ist das dann nicht noch schlimmer?
    • Ein realistischeres Workaround könnte sein, die Grace Time ausreichend lang zu setzen oder umgekehrt die maximale Verbindungszahl so anzupassen, dass die Erfolgswahrscheinlichkeit eines Angriffs in eine so ferne Zukunft rückt, dass sich der Versuch nicht lohnt
    • Könnte ein stündlicher Kaltneustart von sshd die Ausnutzbarkeit verringern oder erschweren?
  • Für Debian 12 ist ein Patch erschienen, Debian 11 ist nicht betroffen
    https://security-tracker.debian.org/tracker/CVE-2024-6387

    • Focal (20.04) scheint keine betroffene Version zu sein, Jammy (22.04) dagegen wohl schon
    • Ich habe gerade auf einem Debian-12-Server apt update und upgrade ausgeführt, und das einzige aktualisierte Paket war OpenSSH
    • Ich habe bestätigt, dass auch in Pi OS bullseye ein aktualisiertes openssh angekommen ist
  • Eine wirklich gute Entdeckung
    Ich bin zwar nicht selbst in dieser Rolle tätig, aber in der Sicherheitsforschung hat man oft den Eindruck: Um „zu gewinnen“, reicht es nicht, ein einzelnes Problem zu finden und zu beheben oder dafür belohnt zu werden, sondern man muss die gesamte Kette bis hin zum Remote-Zugriff finden.
    Eigentlich sollte es doch ausreichen, nur eine einzelne Lücke zu finden, etwa eine Memory Corruption oder einen Sandbox-Escape. Heute gibt es aber so viele kleine Probleme, dass man vielleicht erst einen Hack bis zum Ende demonstrieren muss, damit die Leute ihn wirklich ernst nehmen oder ein Bug Bounty zahlen.

    • Es gibt viele angehende Sicherheitsforscher, die ein nicht ausnutzbares Problem finden und dafür eine CVE-Nummer, Anerkennung oder sogar eine Belohnung verlangen.
      Wenn zum Beispiel eine App bei falsch vertrauenswürdig behandelten Eingaben abstürzt, sie aber ihrer Natur nach gar nicht dafür gedacht ist, Angreifern ausgesetzt zu sein, und das realistisch auch nicht passieren wird, sehen die meisten das einfach als Bug, nicht als Sicherheits-Bug. Es wäre gut, ihn zu beheben, aber es ist nicht dasselbe Niveau, und solche Dinge sind auch nicht besonders schwer zu finden.
      Deshalb muss man zwischen „echten“ Sicherheits-Bugs wie in diesem Fall und Bugs ohne Sicherheitsauswirkung unterscheiden, und es ist sehr wichtig, nachzuweisen, dass ein Problem ausnutzbar ist.
      Bugs ohne Sicherheitsauswirkung wird es endlos geben, daher wird diese Nachweisanforderung wohl nicht so bald verschwinden.
    • Aus einer anderen Perspektive: Angenommen, ich habe eine Serialisierungs-/Deserialisierungsbibliothek geschrieben, die verwundbar wird, wenn man ihr nicht vertrauenswürdige Daten gibt.
      Das ist so designt; Nutzer können alles serialisieren und deserialisieren, bis hin zu Lambda-Funktionen. Meine Bibliothek ist nur für die Verarbeitung von Daten aus vertrauenswürdigen Quellen gedacht.
      Soweit ich weiß, verwendet niemand diese Bibliothek zur Verarbeitung nicht vertrauenswürdiger Daten. Eine populäre Bibliothek nutzt meine Bibliothek zum Lesen von Konfigurationsdateien, aber sie betrachten Konfigurationsdateien als vertrauenswürdige Daten. Und es ist nicht meine Aufgabe zu kontrollieren, wie andere meine Bibliothek verwenden.
      Wäre es in so einem Fall richtig, für mein Projekt eine CVE mit höchster Priorität zu registrieren, weil es darin eine Remote-Code-Execution-Schwachstelle gibt?
    • Ich war schon auf der meldenden Seite, und eine „ausnutzbare Schwachstelle“ und eine „Sicherheitsschwäche, die irgendwann zu einer ausnutzbaren Schwachstelle führen könnte“, sind sehr verschieden.
      Belohnungen werden immer für die erste Kategorie gezahlt. Berichte der zweiten Kategorie können ohne Proof of Concept oder Nachweis der Ausnutzbarkeit sogar dem eigenen Ruf oder Signal schaden.
      Schwächen, die erst ausnutzbar werden, wenn bestimmte Bedingungen erfüllt sind, gibt es fast immer. Auch bei Wettbewerben wie Pwn2Own sieht man häufig, dass mehrere Schwachstellen verkettet werden, um am Ende ein Gerät zu übernehmen, und dass sie jahrelang ungepatcht bleiben. Forscher lassen solche Schwächen mitunter lange liegen, um die Wirkung zu maximieren.
      Traurig, aber so ist die Realität.
    • Nach dem Sicherheitsmotto: POC || GTFO
    • Käufer zahlen für Ergebnisse. Vendoren zahlen zwar auch für einzelne Glieder der Kette.
  • OpenSSH-Release-Notes: https://www.openssh.com/txt/release-9.8
    Minimaler Patch für alle, die nicht upgraden können oder wollen: https://marc.info/?l=oss-security&m=171982317624594&w=2

    • „Eine Ausnutzung auf 64-Bit-Systemen wird ebenfalls für möglich gehalten, ist zum jetzigen Zeitpunkt jedoch nicht nachgewiesen.“