CVSS 9.8 CVE-2023-25136
CVSS 9.8 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
OpenSSH server (sshd) 9.1 introduced a double-free vulnerability during options.kex_algorithms handling. This is fixed in OpenSSH 9.2. The double free can be leveraged, by an unauthenticated remote attacker in the default configuration, to jump to any location in the sshd address space.
One third-party report states “remote code execution is theoretically possible.”
Background: OpenSSH Compatibility Code
OpenSSH maintains a compat (compatibility) subsystem that detects the connecting
client’s SSH implementation by inspecting its version string (sent as the first line of
the SSH handshake, e.g., SSH-2.0-PuTTY_Release_0.64). Different SSH clients have
historically had different quirks, non-standard behaviors, or bugs that OpenSSH works
around via client-specific code paths.
The function compat_kex_proposal() applies these workarounds to the key exchange
(kex) algorithm list before it is sent to the client. It takes the server’s default
algorithm list and modifies it based on which client is connecting — for example,
removing algorithms that a known-buggy client version handles incorrectly.
In OpenSSH 9.1, a bug was introduced in this function: under certain conditions, a pointer to an algorithm list string is freed twice (double-free). This is a pre-authentication code path — it executes during the initial key exchange, before the client has provided any credentials.
Why PuTTY_Release_0.64 Triggers the Vulnerability
The PoC exploit uses PuTTY_Release_0.64 as the client version string specifically
because PuTTY 0.64 has known compatibility quirks that activate a specific code path
in OpenSSH’s compat_kex_proposal(). When the server identifies a connecting client
as PuTTY 0.64, it enters a branch that modifies the kex algorithm list in a way that
triggers the double-free in the 9.1 implementation.
The vulnerability is in OpenSSH’s server code, not in PuTTY — PuTTY 0.64 is simply a version string that activates the vulnerable branch. Any SSH client (or custom script) can present this version string.
The Double-Free Mechanism
A double-free occurs when free() is called on the same memory address twice:
compat_kex_proposal()allocates a new string (a modified kex algorithm list)It frees the original string
Due to the bug in 9.1, the original string pointer is freed a second time in certain code paths
The allocator’s internal metadata (the doubly-linked free-list) is corrupted
On a modern system with a secure allocator (e.g., OpenBSD’s malloc, or modern
glibc with hardening), a double-free is typically detected and the process is killed
immediately — resulting in a Denial of Service (DoS) of the sshd process.
On systems without hardened allocator protections, the corrupted free-list can potentially be exploited to write attacker-controlled data to arbitrary memory locations — which is why JFrog rated the theoretical CVSS at 9.8 (RCE possible).
OpenSSH itself considers RCE very unlikely because:
The vulnerability occurs in the unprivileged pre-auth process (sshd uses privilege separation: the vulnerable code runs in a sandboxed child process)
That process is subject to
chroot(2)on most platformsOn Linux, it is further constrained by seccomp sandbox
Proof of Concept (DoS)
JFrog’s PoC triggers the double-free causing a DoS. The exploit works by:
Opening a paramiko transport connection to the target sshd
Presenting
SSH-2.0-PuTTY_Release_0.64as the client version stringInitiating the key exchange — this causes the server to call
compat_kex_proposal()The double-free occurs and sshd crashes (or is killed by the allocator)
import paramiko
VICTIM_IP = "127.0.0.1"
CLIENT_ID = "PuTTY_Release_0.64"
def main():
transport = paramiko.Transport(VICTIM_IP)
transport.local_version = f"SSH-2.0-{CLIENT_ID}"
transport.connect(username='', password='')
if __name__ == "__main__":
main()
Note
The username='' and password='' arguments are irrelevant — the double-free
occurs during the key exchange phase, which completes before any authentication
attempt. The transport.connect() call triggers the kex exchange and then fails
(or the server crashes) before authentication proceeds.
SSH-MITM Audit Command
SSH-MITM has the PoC integrated as an audit command:
$ ssh-mitm audit CVE-2023-25136 --host 192.168.0.1
OK -> server seems vulnerable
Mitigation
Upgrade to OpenSSH 9.2 or later.
Release Notes 9.2
Note
fix a pre-authentication double-free memory fault introduced in OpenSSH 9.1. This is not believed to be exploitable, and it occurs in the unprivileged pre-auth process that is subject to chroot(2) and is further sandboxed on most major platforms.