
Dies ist ein POC, den ich für CVE-2024-6387 geschrieben habe.
Qualys-Sicherheitshinweis
regreSSHion: RCE im OpenSSH-Server auf glibc-basierten Linux-Systemen (CVE-2024-6387)
Zusammenfassung SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3 (Debian 3.0r6, von 2005)
Es braucht nur einen Vertrauensvorschuss
-- The Interrupters, "Leap of Faith"
Vorbemerkung: OpenSSH ist eine der sichersten Software der Welt; diese Schwachstelle ist ein Ausrutscher in einer ansonsten nahezu fehlerfreien Implementierung. Ihr Defense-in-Depth-Design und ihr Code sind ein Vorbild und eine Inspiration, und wir danken den Entwicklern von OpenSSH für ihre vorbildliche Arbeit.
Wir haben eine Schwachstelle (eine Signalhandler-Race-Condition) im Server von OpenSSH (sshd) entdeckt: Wenn ein Client sich nicht innerhalb von LoginGraceTime Sekunden (standardmäßig 120, bei alten OpenSSH- Versionen 600) authentifiziert, wird der SIGALRM-Handler von sshd asynchron aufgerufen, aber dieser Signalhandler ruft verschiedene Funktionen auf, die nicht async-signal-sicher sind (zum Beispiel syslog()). Diese Race-Condition betrifft sshd in seiner Standardkonfiguration.
Bei der Untersuchung stellten wir fest, dass diese Schwachstelle tatsächlich eine Regression von CVE-2006-5051 ist („Signal handler race condition in OpenSSH before 4.4 allows remote attackers to cause a denial of service (crash), and possibly execute arbitrary code“), die 2006 von Mark Dowd gemeldet wurde.
Diese Regression wurde im Oktober 2020 (OpenSSH 8.5p1) durch den Commit 752250c („revised log infrastructure for OpenSSH“) eingeführt, der versehentlich ein „#ifdef DO_LOG_SAFE_IN_SIGHAND“ aus sigdie() entfernte, einer Funktion, die direkt vom SIGALRM-Handler von sshd aufgerufen wird. Mit anderen Worten:
OpenSSH < 4.4p1 ist anfällig für diese Signalhandler-Race-Condition, wenn es nicht mit einem Backport-Patch gegen CVE-2006-5051 versehen ist oder nicht gegen CVE-2008-4109 gepatcht ist, das eine fehlerhafte Korrektur für CVE-2006-5051 war;
4.4p1 <= OpenSSH < 8.5p1 ist nicht anfällig für diese Signalhandler-Race-Condition (weil das „#ifdef DO_LOG_SAFE_IN_SIGHAND“, das durch den Patch für CVE-2006-5051 zu sigdie() hinzugefügt wurde, diese unsichere Funktion in einen sicheren _exit(1)-Aufruf verwandelt hat);
8.5p1 <= OpenSSH < 9.8p1 ist erneut anfällig für diese Signalhandler-Race-Condition (weil das „#ifdef DO_LOG_SAFE_IN_SIGHAND“ versehentlich aus sigdie() entfernt wurde).
Diese Schwachstelle ist auf glibc-basierten Linux-Systemen remote ausnutzbar, wo syslog() selbst async-signal-unsichere Funktionen aufruft (z. B. malloc() und free()): eine nicht authentifizierte Remote-Codeausführung als root, da sie den privilegierten Code von sshd betrifft, der nicht in einer Sandbox läuft und mit vollen Rechten ausgeführt wird. Wir haben keine andere libc und kein anderes Betriebssystem untersucht; OpenBSD ist jedoch bekanntermaßen nicht anfällig, da sein SIGALRM-Handler syslog_r() aufruft, eine async-signal-sicherere Version von syslog(), die 2001 von OpenBSD erfunden wurde.
Um diese Schwachstelle remote auszunutzen (unseres Wissens wurde CVE-2006-5051 zuvor noch nie erfolgreich ausgenutzt), ließen wir uns von einem visionären Paper inspirieren, „Delivering Signals for Fun and Profit“, das 2001 von Michal Zalewski veröffentlicht wurde:
https://lcamtuf.coredump.cx/signals.txt
Dennoch standen wir sofort vor drei großen Problemen:
Aus theoretischer Sicht müssen wir einen nützlichen Codepfad finden, der, wenn er zur richtigen Zeit von SIGALRM unterbrochen wird, sshd in einem inkonsistenten Zustand hinterlässt, und wir müssen diesen inkonsistenten Zustand dann im SIGALRM-Handler ausnutzen.
Aus praktischer Sicht müssen wir einen Weg finden, diesen nützlichen Codepfad in sshd zu erreichen, und unsere Chancen maximieren, ihn zur richtigen Zeit zu unterbrechen.
Aus zeitlicher Sicht müssen wir einen Weg finden, unsere Chancen, diesen nützlichen Codepfad zur richtigen Zeit zu unterbrechen, remote weiter zu erhöhen.
Um uns auf diese drei Probleme zu konzentrieren, ohne sofort gegen alle modernen Betriebssystemschutzmechanismen (insbesondere ASLR und NX) kämpfen zu müssen, entschieden wir uns, zunächst alte OpenSSH-Versionen auf i386 auszunutzen und dann, basierend auf diesen Erfahrungen, aktuelle Versionen:
Zuerst „SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3“, aus „debian-30r6-dvd-i386-binary-1_NONUS.iso“: Dies ist die erste Debian-Version, bei der Privilegientrennung standardmäßig aktiviert ist und die gegen alle kritischen Schwachstellen dieser Ära gepatcht ist (insbesondere CVE-2003-0693 und CVE-2002-0640).
Um diese Version remote auszunutzen, unterbrechen wir einen Aufruf von free() mit SIGALRM (im Public-Key-Parsing-Code von sshd), hinterlassen den Heap in einem inkonsistenten Zustand und nutzen diesen inkonsistenten Zustand während eines weiteren Aufrufs von free() im SIGALRM-Handler aus.
In unseren Experimenten dauert es im Durchschnitt ~10.000 Versuche, diese Race-Condition zu gewinnen; d. h. mit 10 Verbindungen (MaxStartups), die pro 600 Sekunden (LoginGraceTime) akzeptiert werden, dauert es im Durchschnitt etwa 1 Woche, um eine entfernte Root-Shell zu erhalten.
Zweitens „SSH-2.0-OpenSSH_4.2p1 Debian-7ubuntu3“, aus „ubuntu-6.06.1-server-i386.iso“: Dies ist die letzte Ubuntu-Version, die noch anfällig für CVE-2006-5051 ist („Signal handler race condition in OpenSSH before 4.4“).
Um diese Version remote auszunutzen, unterbrechen wir einen Aufruf von pam_start() mit SIGALRM, hinterlassen eine der PAM-Strukturen in einem inkonsistenten Zustand und nutzen diesen inkonsistenten Zustand während eines Aufrufs von pam_end() im SIGALRM-Handler aus.
In unseren Experimenten dauert es im Durchschnitt ~10.000 Versuche, diese Race-Condition zu gewinnen; d. h. mit 10 Verbindungen (MaxStartups), die pro 120 Sekunden (LoginGraceTime) akzeptiert werden, dauert es im Durchschnitt etwa 1-2 Tage, um eine entfernte Root-Shell zu erhalten.
Schließlich „SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2“, aus „debian-12.5.0-i386-DVD-1.iso“: Dies ist die aktuelle stabile Debian-Version, und sie ist anfällig für die Regression von CVE-2006-5051.
Um diese Version remote auszunutzen, unterbrechen wir einen Aufruf von malloc() mit SIGALRM (im Public-Key-Parsing-Code von sshd), hinterlassen den Heap in einem inkonsistenten Zustand und nutzen diesen inkonsistenten Zustand während eines weiteren Aufrufs von malloc() im SIGALRM-Handler aus (genauer gesagt in syslog()).
In unseren Experimenten dauert es im Durchschnitt ~10.000 Versuche, diese Race-Condition zu gewinnen, also ~3-4 Stunden bei 100 Verbindungen (MaxStartups), die pro 120 Sekunden (LoginGraceTime) akzeptiert werden. Letztendlich dauert es im Durchschnitt ~6-8 Stunden, um eine entfernte Root-Shell zu erhalten, da wir die Adresse der glibc nur in der Hälfte der Fälle richtig erraten können (wegen ASLR).
Diese Forschung befindet sich noch in Arbeit:
wir haben ausschließlich virtuelle Maschinen angegriffen, keine Bare-Metal-Server, über eine weitgehend stabile Netzwerkverbindung (~10 ms Paket-Jitter);
wir sind überzeugt, dass verschiedene Aspekte unserer Exploits erheblich verbessert werden können;