A 32-bit SSH client trusting a hostile server's math homework and getting handed a 4-byte buffer to write 200MB into
libssh2 ≤ 1.11.1 calls libssh2_publickey_list_fetch() to enumerate keys from a server's publickey subsystem. The parser reads an attacker-controlled 32-bit num_attrs from the wire and computes num_attrs * sizeof(libssh2_publickey_attribute) to size an allocation — with no overflow check. On 32-bit builds, num_attrs = 0x0ccccccd wraps the multiplication to 4 bytes, after which the attribute-parsing loop happily writes past the tiny heap chunk. The public PoC (bikini/exploitarium) demonstrates calc.exe execution on Win32 and a separate free/double-free chain on Win64 via a malformed version response. The bug lives in src/publickey.c; everything that links libssh2 — curl, git, libcurl-based agents, OpenWrt droplets, embedded clients — inherits it.
Vendor HIGH (7.0) is *defensible but optimistic*. The CVSS vector encodes the reality: AV:N/AC:H/PR:N/UI:N, meaning network reachable but high complexity because you have to either coerce a victim to connect to your malicious SSH server or MITM an existing connection. The integrity/confidentiality impact is :L only because the primary outcome is corrupted client memory; availability is :H because you can absolutely crash the client. The real-world ceiling depends on whether the libssh2 build is 32-bit — increasingly rare on servers, still common on embedded routers/IoT and some Windows installer toolchains. For a 64-bit Linux fleet running git-over-ssh against trusted forges, this is a backlog item, not a fire.
4 steps from start to impact.
Stand up a malicious SSH server
bikini/exploitarium) that advertises the publickey subsystem and answers publickey-list with a forged response. Off-the-shelf libssh/Twisted-conch are trivial to modify; the PoC ships a Python replay runner.- Public reachability or position to redirect victim
- Ability to speak SSH transport up through subsystem negotiation
- No skill barrier — PoC is public
publickey-list responses.Coerce a libssh2-based client to connect
libssh2_publickey_init(). Most curl/git/scp paths never touch this subsystem.- Victim uses a libssh2 client that calls publickey-subsystem APIs
- Victim connects to attacker-controlled host (phishing repo URL, DNS poison, MITM)
- Vast majority of libssh2 consumers (curl, git, ansible via paramiko-not-libssh2) never invoke the publickey subsystem
- Known-hosts pinning blocks naive MITM
- Most enterprise SSH traffic terminates at trusted bastions
Send the overflow trigger
num_attrs = 0x0ccccccd. On a 32-bit libssh2 build, num_attrs * sizeof(libssh2_publickey_attribute) wraps to a 4-byte heap allocation. The follow-on parser loop then streams attacker-controlled key/value pairs past the chunk boundary.- Target compiled as ILP32 / 32-bit
- Heap allocator returns chunk without trap-guard
- 64-bit builds do not wrap on this multiplication — the dominant deployment on modern Linux/Windows is immune to *this* primitive
- Hardened allocators (scudo, GWP-ASan, Windows segment heap) raise reliability cost
Pivot heap corruption to code execution
calc.exe proven on Win32. Achieving the same on hardened 64-bit Linux requires the *separate* free-chain bug also documented in the PoC (Win64 path), which is a different primitive against libssh2_publickey_list_free().- ASLR/CFG bypass info-leak or known-target
- Writable function-pointer adjacent in heap layout
- CFG/CET on Windows, Intel CET shadow stacks, fortify_source on Linux — none are silver bullets but each costs reliability
- Target process privilege is whatever the libssh2 caller runs as — usually a user-level CLI, not SYSTEM
The supporting signals.
| In-the-wild exploitation | None observed. Disclosed 2026-06-28; no campaign telemetry from CrowdStrike, Mandiant, or KEV. |
|---|---|
| Public PoC | bikini/exploitarium — working Win32 RCE (calc.exe) plus Win64 free-chain primitive; Python replay runner included. |
| EPSS | 0.00311 (~0.3 percentile band) — minimal exploit-likelihood signal. |
| KEV status | Not listed as of 2026-06-29. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:H — network but AC:H because you must lure a victim or MITM. VulnCheck assigns CVSS v4 8.3. |
| Affected versions | All libssh2 releases through 1.11.1, primarily exploitable on 32-bit (ILP32) builds. 64-bit builds vulnerable to the secondary free-chain only. |
| Fixed version | Patched in upstream master (zero-out list[keys] after growth + reject overflow num_attrs); expect distro backports for 1.11.1-x to land within days — track Debian/Ubuntu/RHEL trackers. |
| Affected code | src/publickey.c → libssh2_publickey_list_fetch() and libssh2_publickey_list_free(). |
| Disclosure | 2026-06-28, credit ashdfrkl per VulnCheck advisory. |
| Downstream blast | curl, git (libcurl-based), Ansible (when libssh2 backend), NetMiko/Nornir, embedded firmwares (OpenWrt, BusyBox-based) — but ONLY callers that actually invoke the publickey subsystem are exposed to the primary RCE path. |
noisgate verdict.
MEDIUM because the decisive factor is that exploitation requires the victim to actively connect to an attacker-controlled SSH server AND the client must both invoke the rarely-used publickey *subsystem* (RFC 4819) AND be a 32-bit build to hit the primary overflow primitive. The compounding of three independent narrowing conditions collapses the realistic exposed population well below what vendor HIGH implies.
Why this verdict
- Server-attacker model: this is a malicious-server bug. Defender exposure is gated by either (a) users initiating SSH to untrusted hosts or (b) attacker holding MITM on a TLS-less hop — most enterprise SSH goes to known-hosts-pinned bastions, collapsing the reachable population.
- Subsystem rarity: the publickey subsystem (RFC 4819) is *not* used during normal pubkey auth — curl/git/scp/sftp do not touch it. Only key-management tooling and bespoke automation invoke
libssh2_publickey_init(). Most libssh2 callers in a 10k-host fleet are immune by API surface. - 32-bit dependency: the primary RCE primitive requires the multiplication to wrap, which only happens on ILP32 builds. Modern server fleets are 64-bit; remaining 32-bit exposure is in embedded/IoT/router firmware, which is a real but bounded population.
- Role multiplier — typical libssh2 consumer (curl/git client on workstation): chain fails at step 2 (subsystem not invoked). Blast radius: none.
- Role multiplier — automation/key-management server using publickey subsystem on 64-bit Linux: primary chain fails at step 3; secondary free-chain may succeed but requires harder primitives. Blast radius: single-host RCE as the agent user. Not a fleet pivot unless the agent itself holds privileged credentials — which is the worst plausible high-value-role outcome and floors this at HIGH for *those specific hosts*. Fleet-wide, that subpopulation is well under 1%.
- Role multiplier — embedded 32-bit network device (OpenWrt router, IoT gateway) speaking outbound SSH: primary chain succeeds if device initiates publickey-subsystem queries. Blast radius: device compromise. Real fleets rarely have these devices dialing arbitrary SSH endpoints.
- EPSS 0.00311 + not-KEV + no observed exploitation = no urgency multiplier from external threat data.
Why not higher?
HIGH would require either active exploitation, a fleet-scale role multiplier on a canonical high-value component, or a client-attacker model. This is a client-side bug in a library where the typical caller doesn't even touch the affected API, on the increasingly rare 32-bit build target — none of those upgrade conditions hold.
Why not lower?
LOW would mean immaterial impact; arbitrary code execution in a privileged automation agent is not immaterial. The Win32 PoC demonstrably pops calc.exe, and embedded fleets still carry 32-bit libssh2. Real RCE in real (if narrow) populations earns MEDIUM, not LOW.
What to do — in priority order.
- Inventory libssh2 consumers by architecture — Within the noisgate MEDIUM window (no mitigation SLA, 365-day remediation), inventory hosts running libssh2-linked binaries. Use
ldd $(which curl git ssh) | grep libssh2on Linux,dumpbin /dependentson Windows. Tag any 32-bit results as priority — that's your primary-RCE exposure. Embedded/router firmware vendors should be queried directly. - Block outbound SSH egress to untrusted destinations — Egress firewall rule: deny TCP/22 outbound except to known bastions, forges (github/gitlab/bitbucket IP ranges), and approved jump hosts. Kills the malicious-server prerequisite for every non-developer host. Implement within 30 days as standard hygiene — useful well beyond this CVE.
- Pin SSH known_hosts and enforce StrictHostKeyChecking=yes — For any libssh2-using automation, ship a managed
known_hostsand setLIBSSH2_HOSTKEY_HASH_*verification in calling code. Prevents trivial MITM redirect onto an attacker server. - Update libssh2 when distro packages land — Track CVE-2026-58050 in Debian/Ubuntu/RHEL/Alpine security trackers; deploy the backport during normal monthly patch cycle. Per noisgate remediation SLA for MEDIUM, complete within 365 days — accelerate to 30 days for any host you tagged as 32-bit or running publickey-subsystem automation.
- Audit code for publickey-subsystem callers — Grep your codebase and dependencies for
libssh2_publickey_init,libssh2_publickey_list_fetch,libssh2_publickey_add. If you find none, you can defer this CVE to backlog hygiene with documented rationale.
- Disabling SSH server pubkey auth — irrelevant, this is a *client-side* bug triggered by a malicious server.
- Rotating SSH keys — the bug is in protocol parsing, not key material.
- WAF / IDS signatures on encrypted SSH traffic — you cannot inspect inside the transport layer without terminating it.
- Disabling the publickey-authentication method in sshd_config — wrong subsystem; this is RFC 4819 publickey-*subsystem* used for key management, not pubkey auth.
Crowdsourced verification payload.
Run on each Linux/macOS target host as any user (no privileges required). Invoke as ./check_cve_2026_58050.sh. The script locates the libssh2 shared library, extracts its version string, checks whether the host is 32-bit, and reports VULNERABLE / PATCHED / UNKNOWN. For Windows targets, port the logic against libssh2.dll version metadata.
#!/usr/bin/env bash
# check_cve_2026_58050.sh — libssh2 publickey-subsystem integer overflow
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN
set -u
FIXED_MAJOR=1
FIXED_MINOR=11
FIXED_PATCH=2 # first release expected to carry the fix; adjust when upstream tags
find_lib() {
for p in /usr/lib /usr/lib64 /usr/local/lib /lib /lib64 /opt; do
find "$p" -maxdepth 4 -name 'libssh2.so*' 2>/dev/null
done | sort -u
}
ver_from_lib() {
local lib="$1"
# libssh2 embeds version via LIBSSH2_VERSION macro -> strings grep
strings "$lib" 2>/dev/null | grep -Eo '^libssh2/[0-9]+\.[0-9]+\.[0-9]+' | head -1 | cut -d/ -f2
}
is_32bit() {
local lib="$1"
file "$lib" 2>/dev/null | grep -q 'ELF 32-bit'
}
verdict=UNKNOWN
found=0
while IFS= read -r lib; do
[ -z "$lib" ] && continue
found=1
ver=$(ver_from_lib "$lib")
arch32=no; is_32bit "$lib" && arch32=yes
if [ -z "$ver" ]; then
echo "[?] $lib — version string not found (arch32=$arch32)"
[ "$verdict" = UNKNOWN ] && verdict=UNKNOWN
continue
fi
IFS=. read -r MA MI PA <<<"$ver"
if [ "$MA" -gt "$FIXED_MAJOR" ] || \
{ [ "$MA" -eq "$FIXED_MAJOR" ] && [ "$MI" -gt "$FIXED_MINOR" ]; } || \
{ [ "$MA" -eq "$FIXED_MAJOR" ] && [ "$MI" -eq "$FIXED_MINOR" ] && [ "$PA" -ge "$FIXED_PATCH" ]; }; then
echo "[+] $lib v$ver (arch32=$arch32) PATCHED"
[ "$verdict" != VULNERABLE ] && verdict=PATCHED
else
echo "[!] $lib v$ver (arch32=$arch32) VULNERABLE — primary RCE primitive applies on 32-bit"
verdict=VULNERABLE
fi
done < <(find_lib)
if [ "$found" -eq 0 ]; then
echo "[?] libssh2 not found in standard paths — check statically linked binaries (curl, git, etc.)"
echo UNKNOWN
exit 2
fi
echo "$verdict"
case "$verdict" in
PATCHED) exit 0 ;;
VULNERABLE) exit 1 ;;
*) exit 2 ;;
esac
If you remember one thing.
libssh2_publickey_init() — those two subpopulations get pulled into a 30-day patch window because they hold the actual RCE risk. Second, enforce egress-restriction to known SSH destinations (bastions + sanctioned forges) within 30 days as standing hygiene; it nullifies the malicious-server prerequisite for the entire fleet. Everything else — 64-bit Linux running curl/git/Ansible — rolls in with normal distro updates over the next quarter. If KEV-lists this or in-the-wild exploitation surfaces, escalate immediately to the CRITICAL hours-window.Sources
- VulnCheck advisory — libssh2 publickey integer overflow
- NVD — CVE-2026-58050
- PoC repo — bikini/exploitarium libssh2 publickey-list calc PoC
- RedPacket Security — CVE alert
- CIRCL Vulnerability-Lookup — CVE-2026-58050
- GHSA-mf77-5hj2-98w9 (libssh2 advisory mirror)
- RFC 4819 — SSH Public Key Subsystem
- libssh2 upstream — src/publickey.c
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.