← Back to Feed CACHED · 2026-06-23 09:46:52 · CACHE_KEY CVE-2026-55200
CVE-2026-55200 · CWE-680 · Disclosed 2026-06-17

libssh2 - Out-of-Bounds Write via Unchecked packet_length in transport

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

A buffer waiting to receive an SSH packet trusts the attacker's stated packet length without sanity-checking it

libssh2's ssh2_transport_read() in transport.c reads the 4-byte packet_length field straight off the wire and uses it to size buffers and drive copy logic without an upper-bound check. A malicious peer supplies a huge value, an integer math step wraps, and the library performs an out-of-bounds heap write during the very first transport negotiation — *before* key exchange, *before* authentication, *before* the user types anything. This is a classic CWE-680 (integer overflow to buffer overflow) primitive, and with the right heap shaping it gives remote code execution in the process embedding libssh2. All versions through 1.11.1 are affected; the fix landed in upstream commit 7acf3df.

The vendor's CRITICAL/9.2 rating is honest for the *primitive* — pre-auth, network-reachable memory corruption is exactly what 9.x is supposed to mean. What the CVSS doesn't show is the direction of the arrow: libssh2 is overwhelmingly used as a client library (curl --ssh, PHP's ssh2 extension, libgit2 SSH transport, some backup agents, embedded device management clients), so the victim has to initiate the connection — to either an attacker-controlled SSH server or an on-path attacker who can intercept TCP/22. That's why we hold this at HIGH (8.2) instead of CRITICAL: the bug is real, the impact is real, but the *trigger* requires the attacker to be on the path or to lure the client. Reliability is also non-trivial — the CVSS 4.0 vector explicitly carries AT:P, meaning heap-layout conditions must hold for code execution.

"Real bug, real RCE primitive — but it's client-side. You only get popped if you SSH into a hostile server or someone MitMs port 22."
02 · The Attack Path

4 steps from start to impact.

STEP 01

Position as a malicious SSH endpoint

Attacker stands up an SSH listener on TCP/22 (or any port a victim client targets) and serves their own banner and KEXINIT packet. Alternatively, they MitM an existing client→server SSH flow on an untrusted network segment (open Wi-Fi, compromised ISP, BGP hijack, rogue jump host). No credentials, no certificates, nothing — the attacker just needs the TCP socket.
Conditions required:
  • Attacker controls a server the victim connects to, OR sits on the network path
  • Victim runs a libssh2-based client ≤ 1.11.1
Where this breaks in practice:
  • Inbound clients to attacker servers require luring (typo-squat git mirrors, phishing repo URLs, malicious known_hosts entries)
  • On-path MitM of port 22 inside enterprise networks is non-trivial behind segmented VLANs and zero-trust egress
Detection/coverage: Egress monitoring to non-allowlisted SSH endpoints; corporate proxies typically do not inspect SSH
STEP 02

Send oversized packet_length during transport read

Before key exchange completes, the victim's ssh2_transport_read() parses the 4-byte length prefix of the attacker's SSH binary packet. The attacker sends a value crafted to wrap during the buffer-size arithmetic — e.g., near 0xFFFFFFFF — so the resulting allocation is small but the subsequent copy walks far past its end. Weaponization: a ~50-line Python PoC using socket is sufficient to demonstrate the crash; expect public PoCs within days.
Conditions required:
  • TCP handshake completed
  • Victim libssh2 calls _libssh2_transport_read on attacker bytes
Where this breaks in practice:
  • None at this stage — the bug is pre-auth and pre-kex
Detection/coverage: IDS signatures on SSH packet_length > MAX_SSH_PACKET (35000) are trivial to write but not deployed by default
STEP 03

Shape the heap for write-what-where

OOB heap write becomes useful only when the attacker can predict what sits adjacent in memory. Real exploitation requires grooming the libssh2 allocator state (often glibc ptmalloc, sometimes jemalloc on Alpine/BSD) so an overwritable function pointer, vtable, or _libssh2_session struct field lands next to the corrupted chunk. Tooling: standard heap-grooming techniques from house-of-* family; researchers like *Tristan Madani* (credited reporter) have not yet published a full chain.
Conditions required:
  • Ability to send multiple packets before crash
  • Knowledge of target libc / allocator / ASLR posture
Where this breaks in practice:
  • ASLR + PIE on modern Linux distros
  • Hardened allocators (scudo on Android, GWP-ASan)
  • Single-shot clients (e.g., a one-off git clone) give few grooming opportunities
Detection/coverage: EDR rarely sees in-process heap corruption; crash telemetry (ABRT, Windows Error Reporting) may show repeated SIGSEGV in the libssh2 user
STEP 04

Achieve RCE in the embedding process

Once the corrupted pointer is dereferenced, control transfers to the attacker. The privilege gained equals whatever ran the SSH client: a CI runner shell, a backup agent (often root/SYSTEM), curl in a script (the calling user), or PHP-FPM on a web host using ssh2_connect(). From there, standard post-exploitation — credential theft, lateral movement, supply-chain pivot if the popped host is a build node.
Conditions required:
  • Successful step 3
Where this breaks in practice:
  • RELRO/Fortify/CFI in modern binaries reduces gadget viability
  • Sandboxed clients (firejail, seccomp) limit damage
Detection/coverage: EDR on parent process (curl spawning shell, ssh2_connect spawning sh) catches the post-exploitation tail, not the corruption itself
03 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed as of 2026-06-23. No KEV entry, no incident reports, no honeypot telemetry.
Public PoCNo public weaponized exploit yet. Researcher *Tristan Madani* published the advisory but withheld a chain. Expect crash-PoCs within 1-2 weeks.
EPSS0.00552 (~0.55%) — low, consistent with no public exploit and client-side trigger model
KEV statusNot listed by CISA as of 2026-06-23
CVSS vectorCVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N → 9.2. Note AT:P — attack requirements present (heap grooming for full RCE)
Affected versionslibssh2 through 1.11.1 (all currently shipped releases at disclosure)
Fixed versionUpstream commit 7acf3df. Distro backports: watch RHEL libssh2, Debian/Ubuntu libssh2-1, Alpine libssh2, Amazon Linux libssh2. Also rebuild downstream consumers — curl, libgit2, PHP ssh2, php-ssh2.
Exposure (Shodan/Censys)Not directly scannable — libssh2 is a client library; it does not expose a listening port. Inventory must come from SBOM / package manager, not network scans.
Disclosure date2026-06-17
ReporterTristan Madani (independent researcher)
04 · The Call

noisgate verdict.

Final Verdict
DOWNGRADED to HIGH (8.2/10)

Downgrading from CRITICAL/9.2 to HIGH/8.2 because the single most decisive factor is the *direction of the connection*: libssh2 is a client library, so the attacker must lure the victim to a malicious server or sit on-path — there is no unsolicited internet-facing trigger. The bug stays in HIGH (not lower) because the affected component is embedded in build agents, backup tooling, and PHP/curl-based automation where a single popped host pivots into supply-chain or fleet-scale impact.

HIGH technical root cause and patch identification
HIGH client-side trigger model and exposure shape
MEDIUM exploitation reliability — depends on heap shape per embedding
LOW near-term in-the-wild use — no public chain yet

Why this verdict

  • Client-side trigger, not server-side: libssh2 listens for nothing; the victim must initiate the SSH session. That removes the "internet-wide spray" failure mode that defines true CRITICAL network bugs like Log4Shell or BlueKeep.
  • AT:P is real: CVSS 4.0 explicitly flags attack requirements. Crashing the client is trivial; achieving RCE requires per-target heap grooming against modern allocators with ASLR.
  • No PoC, no KEV, EPSS 0.55%: none of the three independent exploitation indicators is firing. Vendor 9.2 reflects the primitive's theoretical ceiling, not the realistic near-term threat.
  • Role multiplier — CI/CD and build agents: Jenkins/GitLab runners using libgit2+libssh2 to clone from SSH remotes are a canonical supply-chain pivot. A poisoned mirror or hijacked DNS for git.internal lands code execution on the builder, which then signs and publishes artifacts. This is why the floor is HIGH, not MEDIUM.
  • Role multiplier — backup and replication agents: Veeam-style agents, rsync-over-ssh wrappers, and storage replication tools that embed libssh2 typically run as root/SYSTEM and connect to many endpoints. Any one compromised remote pops the backup tier — fleet-scale blast radius.
  • Role multiplier — PHP ssh2 extension on web tier: if a web app calls ssh2_connect() against attacker-controllable hostnames (SSRF-adjacent feature), the web process pops. Narrow but real.

Why not higher?

CRITICAL is reserved for bugs an attacker can ring without invitation — listening services, browser drive-bys, or active mass scanning. This bug requires either victim-initiated connection to a hostile endpoint or on-path interception, neither of which scales like a wormable server-side flaw. No public exploit and EPSS under 1% confirm we are not yet in fire-drill territory.

Why not lower?

MEDIUM would be wrong because the bug is pre-authentication memory corruption in a library shipped in *thousands* of distinct products, including high-value-role components (build agents, backup tooling). A single supply-chain compromise of a popular git mirror combined with this CVE compromises every CI runner that clones it. That blast radius floors the verdict at HIGH per noisgate role-multiplier rules.

05 · Compensating Control

What to do — in priority order.

  1. Inventory every libssh2 consumer via SBOM — You cannot scan for this — libssh2 doesn't listen. Pull from package managers (dpkg -l libssh2-1, rpm -qa | grep libssh2, apk info libssh2), language ecosystems (PHP pecl list, Python packages bundling it, Node native modules), and container image SBOMs (Syft/Grype). Complete within 7 days under the HIGH noisgate mitigation SLA (≤ 30 days).
  2. Restrict outbound SSH to allowlisted endpoints — Force all egress SSH through known jump hosts or a managed proxy. Block direct port-22 egress from CI runners, web tiers, and backup agents to anywhere outside the allowlist. Deploy within the noisgate mitigation SLA of 30 days — this neutralizes the malicious-server vector for managed networks.
  3. Pin git/clone sources to verified internal mirrors — Build pipelines must clone only from your internal artifact registry or mirrored Git server, never from arbitrary upstream URLs. This breaks the supply-chain attack path even if libssh2 is unpatched on the runner.
  4. Enable verbose known_hosts checking and pin host keys — Strict host-key checking ensures an on-path MitM presenting a different SSH server key terminates the connection before ssh2_transport_read() is called against attacker bytes. Configure StrictHostKeyChecking=yes and pre-seed known_hosts.
  5. Rebuild and ship downstream consumers — Patching libssh2 the package is not enough if you ship containers or appliances that statically link or bundle older builds. Rebuild every internal image whose SBOM lists libssh2 ≤ 1.11.1 and push within the noisgate remediation SLA of 180 days.
What doesn't work
  • Network IDS signatures alone — outbound SSH is opaque after the first packet; you cannot inspect content. The malicious length field is sent in cleartext for ~1 packet, but most IDS deployments do not parse SSH binary packets.
  • WAF rules — irrelevant; libssh2 is not behind a WAF, and HTTP-layer controls don't see SSH.
  • Disabling password auth / requiring MFA on SSH — the bug fires *before* authentication. Auth hardening on the server side has zero effect on a vulnerable client.
  • Patching only openssh-client — OpenSSH and libssh2 are independent codebases. The libssh2 fix is in a separate package; an openssh update does not address this CVE.
06 · Verification

Crowdsourced verification payload.

Run this on each Linux/macOS host suspected of using libssh2 — including build runners, web servers, backup agents. Invoke as sudo ./check_libssh2.sh (root required to read all linked binaries). It enumerates package versions, ldconfig entries, and statically embedded copies, then prints VULNERABLE / PATCHED / UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# noisgate: CVE-2026-55200 libssh2 OOB write verifier
# Exit codes: 0 PATCHED, 1 VULNERABLE, 2 UNKNOWN
set -u
FIXED_VER="1.11.2"   # first release expected to carry commit 7acf3df
VULN=0
FOUND=0

compare_ver() {
  # returns 0 if $1 >= $2
  [ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ]
}

echo "[*] Checking package manager records..."
if command -v dpkg >/dev/null 2>&1; then
  while read -r name ver; do
    [ -z "$name" ] && continue
    FOUND=1
    clean=$(echo "$ver" | sed 's/[-+].*//')
    if compare_ver "$clean" "$FIXED_VER"; then
      echo "  [OK]  dpkg $name $ver"
    else
      echo "  [!!]  dpkg $name $ver  <- below $FIXED_VER"
      VULN=1
    fi
  done < <(dpkg-query -W -f='${Package} ${Version}\n' 2>/dev/null | grep -E '^libssh2')
fi

if command -v rpm >/dev/null 2>&1; then
  while read -r line; do
    [ -z "$line" ] && continue
    FOUND=1
    name=$(echo "$line" | awk '{print $1}')
    ver=$(echo "$line" | awk '{print $2}')
    if compare_ver "$ver" "$FIXED_VER"; then
      echo "  [OK]  rpm $name $ver"
    else
      echo "  [!!]  rpm $name $ver  <- below $FIXED_VER (check distro backport)"
      VULN=1
    fi
  done < <(rpm -qa --qf '%{NAME} %{VERSION}\n' 2>/dev/null | grep -E '^libssh2')
fi

if command -v apk >/dev/null 2>&1; then
  while read -r line; do
    [ -z "$line" ] && continue
    FOUND=1
    pkg=$(echo "$line" | sed 's/-[0-9].*//')
    ver=$(echo "$line" | sed "s/^${pkg}-//")
    if compare_ver "$ver" "$FIXED_VER"; then
      echo "  [OK]  apk $pkg $ver"
    else
      echo "  [!!]  apk $pkg $ver"
      VULN=1
    fi
  done < <(apk info -v 2>/dev/null | grep -E '^libssh2')
fi

echo "[*] Scanning linked shared objects..."
for so in $(find /usr /opt /app -name 'libssh2.so*' 2>/dev/null); do
  FOUND=1
  ver=$(strings "$so" 2>/dev/null | grep -oE 'libssh2/[0-9]+\.[0-9]+\.[0-9]+' | head -n1 | sed 's|libssh2/||')
  if [ -z "$ver" ]; then
    echo "  [??]  $so (no embedded version string)"
    continue
  fi
  if compare_ver "$ver" "$FIXED_VER"; then
    echo "  [OK]  $so -> $ver"
  else
    echo "  [!!]  $so -> $ver"
    VULN=1
  fi
done

echo "[*] Hunting for statically linked copies in common binaries..."
for b in $(command -v curl git php node python3 2>/dev/null); do
  if strings "$b" 2>/dev/null | grep -q 'libssh2/'; then
    FOUND=1
    sver=$(strings "$b" | grep -oE 'libssh2/[0-9]+\.[0-9]+\.[0-9]+' | head -n1 | sed 's|libssh2/||')
    if [ -n "$sver" ] && compare_ver "$sver" "$FIXED_VER"; then
      echo "  [OK]  $b statically embeds libssh2 $sver"
    else
      echo "  [!!]  $b statically embeds libssh2 ${sver:-unknown}"
      VULN=1
    fi
  fi
done

if [ "$FOUND" -eq 0 ]; then
  echo "RESULT: UNKNOWN (no libssh2 evidence found on this host)"
  exit 2
fi
if [ "$VULN" -eq 1 ]; then
  echo "RESULT: VULNERABLE"
  exit 1
fi
echo "RESULT: PATCHED"
exit 0
07 · Bottom Line

If you remember one thing.

TL;DR
Monday morning: pull the SBOM for every CI runner, build agent, backup server, and PHP/curl-using host and grep for libssh2 ≤ 1.11.1. You cannot find this with a network scanner — it has to come from package inventory. Under the noisgate mitigation SLA for HIGH (≤ 30 days), block direct outbound SSH from servers to non-allowlisted endpoints and pin Git clone sources to internal mirrors; that alone neutralizes the realistic supply-chain pivot. Under the noisgate remediation SLA for HIGH (≤ 180 days), roll the distro update once your vendor (RHEL, Ubuntu, Alpine, Amazon Linux) publishes a backport and rebuild every container image that bundles libssh2 statically. If a public RCE PoC drops or this lands in KEV, override both deadlines and patch/mitigate within hours — the *primitive* is genuinely 9.2, only the trigger model holds it back.

Sources

  1. GitHub Security Advisory GHSA-r8mh-x5qv-7gg2
  2. VulnCheck advisory
  3. OpenCVE CVE-2026-55200
  4. CVEFeed CVE-2026-55200
  5. Positive Technologies dbugs writeup
  6. Cybersecurity News coverage
  7. GBHackers analysis
  8. libssh2 upstream repository
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.