← Back to Feed CACHED · 2026-06-29 08:29:36 · CACHE_KEY CVE-2026-58050
CVE-2026-58050 · CWE-190 · Disclosed 2026-06-28

libssh2 through 1

ASSESSED — NOISGATE V0.5
Vendor
Reassessed
Verdict:
Do you agree?
01 · The Real Story

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.

"Client-side heap overflow that needs a malicious SSH server AND a 32-bit client — vendor HIGH is generous for 2026 reality."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Stand up a malicious SSH server

Attacker runs a doctored sshd (or PoC harness from 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.
Conditions required:
  • Public reachability or position to redirect victim
  • Ability to speak SSH transport up through subsystem negotiation
Where this breaks in practice:
  • No skill barrier — PoC is public
Detection/coverage: Network IDS won't see anything anomalous in the encrypted channel; only an SSH-aware proxy with deep inspection would notice malformed publickey-list responses.
STEP 02

Coerce a libssh2-based client to connect

Victim must initiate (or be redirected to) an SSH session that invokes the publickey subsystem — note: this is the SECSH-publickey *subsystem* (RFC 4819), NOT routine pubkey authentication. Real callers are sparse: some key-management tooling, a handful of automation agents, and clients explicitly calling libssh2_publickey_init(). Most curl/git/scp paths never touch this subsystem.
Conditions required:
  • Victim uses a libssh2 client that calls publickey-subsystem APIs
  • Victim connects to attacker-controlled host (phishing repo URL, DNS poison, MITM)
Where this breaks in practice:
  • 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
Detection/coverage: EDR/proxy can flag connections to untrusted SSH destinations; host-pinning failures show up in client logs.
STEP 03

Send the overflow trigger

Server replies to the list request with 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.
Conditions required:
  • Target compiled as ILP32 / 32-bit
  • Heap allocator returns chunk without trap-guard
Where this breaks in practice:
  • 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
Detection/coverage: ASAN/MSAN catches in CI; runtime crashes surface as heap corruption signatures in WER/coredumps.
STEP 04

Pivot heap corruption to code execution

PoC overwrites an adjacent callback pointer and redirects control flow — 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().
Conditions required:
  • ASLR/CFG bypass info-leak or known-target
  • Writable function-pointer adjacent in heap layout
Where this breaks in practice:
  • 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
Detection/coverage: EDR memory-protection (Cylance/Defender/CS) flags indirect-call anomalies; libssh2 callers rarely have legitimate JIT regions.
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. Disclosed 2026-06-28; no campaign telemetry from CrowdStrike, Mandiant, or KEV.
Public PoCbikini/exploitarium — working Win32 RCE (calc.exe) plus Win64 free-chain primitive; Python replay runner included.
EPSS0.00311 (~0.3 percentile band) — minimal exploit-likelihood signal.
KEV statusNot listed as of 2026-06-29.
CVSS vectorCVSS: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 versionsAll libssh2 releases through 1.11.1, primarily exploitable on 32-bit (ILP32) builds. 64-bit builds vulnerable to the secondary free-chain only.
Fixed versionPatched 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 codesrc/publickey.clibssh2_publickey_list_fetch() and libssh2_publickey_list_free().
Disclosure2026-06-28, credit ashdfrkl per VulnCheck advisory.
Downstream blastcurl, 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.
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to MEDIUM (5.4/10)

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.

HIGH technical accuracy of the overflow primitive (PoC verified)
HIGH 32-bit-only nature of the primary exploit path
MEDIUM share of libssh2 consumers that actually call the publickey subsystem
LOW exploitability of the secondary Win64 free-chain in hardened environments

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.

05 · Compensating Control

What to do — in priority order.

  1. 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 libssh2 on Linux, dumpbin /dependents on Windows. Tag any 32-bit results as priority — that's your primary-RCE exposure. Embedded/router firmware vendors should be queried directly.
  2. 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.
  3. Pin SSH known_hosts and enforce StrictHostKeyChecking=yes — For any libssh2-using automation, ship a managed known_hosts and set LIBSSH2_HOSTKEY_HASH_* verification in calling code. Prevents trivial MITM redirect onto an attacker server.
  4. 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.
  5. 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.
What doesn't work
  • 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.
06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: don't drop other work for this. The noisgate mitigation SLA for MEDIUM has no fixed deadline — go straight to the 365-day noisgate remediation SLA, with two accelerations. First, run an inventory query this week to identify any 32-bit libssh2 builds (embedded gear, legacy Windows installers) and any code that calls 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

  1. VulnCheck advisory — libssh2 publickey integer overflow
  2. NVD — CVE-2026-58050
  3. PoC repo — bikini/exploitarium libssh2 publickey-list calc PoC
  4. RedPacket Security — CVE alert
  5. CIRCL Vulnerability-Lookup — CVE-2026-58050
  6. GHSA-mf77-5hj2-98w9 (libssh2 advisory mirror)
  7. RFC 4819 — SSH Public Key Subsystem
  8. libssh2 upstream — src/publickey.c
Peer Review

What defenders are saying.

Submit a review attribution: handle + country only
0 flags selected · stored anonymously
Validation Results

Crowdsourced verification outputs.

Results submitted by users who ran the verification payload against their environment.