
This is a POC I wrote for CVE-2024-6387
Qualys Security Advisory
regreSSHion: RCE in OpenSSH's server, on glibc-based Linux systems (CVE-2024-6387)
Summary SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3 (Debian 3.0r6, from 2005)
All it takes is a leap of faith
-- The Interrupters, "Leap of Faith"
Preliminary note: OpenSSH is one of the most secure software in the world; this vulnerability is one slip-up in an otherwise near-flawless implementation. Its defense-in-depth design and code are a model and an inspiration, and we thank OpenSSH's developers for their exemplary work.
We discovered a vulnerability (a signal handler race condition) in OpenSSH's server (sshd): if a client does not authenticate within LoginGraceTime seconds (120 by default, 600 in old OpenSSH versions), then sshd's SIGALRM handler is called asynchronously, but this signal handler calls various functions that are not async-signal-safe (for example, syslog()). This race condition affects sshd in its default configuration.
On investigation, we realized that this vulnerability is in fact a regression of CVE-2006-5051 ("Signal handler race condition in OpenSSH before 4.4 allows remote attackers to cause a denial of service (crash), and possibly execute arbitrary code"), which was reported in 2006 by Mark Dowd.
This regression was introduced in October 2020 (OpenSSH 8.5p1) by commit 752250c ("revised log infrastructure for OpenSSH"), which accidentally removed an "#ifdef DO_LOG_SAFE_IN_SIGHAND" from sigdie(), a function that is directly called by sshd's SIGALRM handler. In other words:
OpenSSH < 4.4p1 is vulnerable to this signal handler race condition, if not backport-patched against CVE-2006-5051, or not patched against CVE-2008-4109, which was an incorrect fix for CVE-2006-5051;
4.4p1 <= OpenSSH < 8.5p1 is not vulnerable to this signal handler race condition (because the "#ifdef DO_LOG_SAFE_IN_SIGHAND" that was added to sigdie() by the patch for CVE-2006-5051 transformed this unsafe function into a safe _exit(1) call);
8.5p1 <= OpenSSH < 9.8p1 is vulnerable again to this signal handler race condition (because the "#ifdef DO_LOG_SAFE_IN_SIGHAND" was accidentally removed from sigdie()).
This vulnerability is exploitable remotely on glibc-based Linux systems, where syslog() itself calls async-signal-unsafe functions (for example, malloc() and free()): an unauthenticated remote code execution as root, because it affects sshd's privileged code, which is not sandboxed and runs with full privileges. We have not investigated any other libc or operating system; but OpenBSD is notably not vulnerable, because its SIGALRM handler calls syslog_r(), an async-signal-safer version of syslog() that was invented by OpenBSD in 2001.
To exploit this vulnerability remotely (to the best of our knowledge, CVE-2006-5051 has never been successfully exploited before), we drew inspiration from a visionary paper, "Delivering Signals for Fun and Profit", which was published in 2001 by Michal Zalewski:
https://lcamtuf.coredump.cx/signals.txt
Nevertheless, we immediately faced three major problems:
From a theoretical point of view, we must find a useful code path that, if interrupted at the right time by SIGALRM, leaves sshd in an inconsistent state, and we must then exploit this inconsistent state inside the SIGALRM handler.
From a practical point of view, we must find a way to reach this useful code path in sshd, and maximize our chances of interrupting it at the right time.
From a timing point of view, we must find a way to further increase our chances of interrupting this useful code path at the right time, remotely.
To focus on these three problems without having to immediately fight against all the modern operating system protections (in particular, ASLR and NX), we decided to exploit old OpenSSH versions first, on i386, and then, based on this experience, recent versions:
First, "SSH-2.0-OpenSSH_3.4p1 Debian 1:3.4p1-1.woody.3", from "debian-30r6-dvd-i386-binary-1_NONUS.iso": this is the first Debian version that has privilege separation enabled by default and that is patched against all the critical vulnerabilities of that era (in particular, CVE-2003-0693 and CVE-2002-0640).
To remotely exploit this version, we interrupt a call to free() with SIGALRM (inside sshd's public-key parsing code), leave the heap in an inconsistent state, and exploit this inconsistent state during another call to free(), inside the SIGALRM handler.
In our experiments, it takes ~10,000 tries on average to win this race condition; i.e., with 10 connections (MaxStartups) accepted per 600 seconds (LoginGraceTime), it takes ~1 week on average to obtain a remote root shell.
Second, "SSH-2.0-OpenSSH_4.2p1 Debian-7ubuntu3", from "ubuntu-6.06.1-server-i386.iso": this is the last Ubuntu version that is still vulnerable to CVE-2006-5051 ("Signal handler race condition in OpenSSH before 4.4").
To remotely exploit this version, we interrupt a call to pam_start() with SIGALRM, leave one of PAM's structures in an inconsistent state, and exploit this inconsistent state during a call to pam_end(), inside the SIGALRM handler.
In our experiments, it takes ~10,000 tries on average to win this race condition; i.e., with 10 connections (MaxStartups) accepted per 120 seconds (LoginGraceTime), it takes ~1-2 days on average to obtain a remote root shell.
Finally, "SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u2", from "debian-12.5.0-i386-DVD-1.iso": this is the current Debian stable version, and it is vulnerable to the regression of CVE-2006-5051.
To remotely exploit this version, we interrupt a call to malloc() with SIGALRM (inside sshd's public-key parsing code), leave the heap in an inconsistent state, and exploit this inconsistent state during another call to malloc(), inside the SIGALRM handler (more precisely, inside syslog()).
In our experiments, it takes ~10,000 tries on average to win this race condition, so ~3-4 hours with 100 connections (MaxStartups) accepted per 120 seconds (LoginGraceTime). Ultimately, it takes ~6-8 hours on average to obtain a remote root shell, because we can only guess the glibc's address correctly half of the time (because of ASLR).
This research is still a work in progress:
we have targeted virtual machines only, not bare-metal servers, on a mostly stable network link (~10ms packet jitter);
we are convinced that various aspects of our exploits can be greatly improved;
we have started to work on an amd64 exploit, which is much harder because of the stronger ASLR.
A few days after we started our work on amd64, we noticed the following bug report (in OpenSSH's public Bugzilla), about a deadlock in sshd's SIGALRM handler:
https://bugzilla.mindrot.org/show_bug.cgi?id=3690
We therefore decided to contact OpenSSH's developers immediately (to let them know that this deadlock is caused by an exploitable vulnerability), we put our amd64 work on hold, and we started to write this advisory.