Vulnerability Analysis

CVE-2026-55200: Critical libssh2 Pre-Auth RCE — What It Is and How to Fix It

Executive Summary

CVE-2026-55200 is a critical pre-authentication heap overflow in libssh2, the SSH client library embedded in curl, PHP, Git GUI clients, backup agents, and a long tail of embedded devices and appliances. By sending a single crafted packet during the SSH handshake — before any credentials are exchanged — an attacker controlling or impersonating an SSH server can corrupt heap memory and potentially execute arbitrary code on any connecting client. CVSS scores range from 9.2 (CVSS 4.0) to 9.8, there is no official tagged release containing the fix yet, and public proof-of-concept code is already available. Every organization that uses automated SSH, SCP, or SFTP transfers should treat this as an urgent priority.


1. What Is This Vulnerability?

libssh2 is a C library that implements the client side of the SSH2 protocol. It is not a standalone tool — it is a dependency baked into hundreds of applications. curl uses it for SCP and SFTP support. PHP's SSH/SFTP extension links it. Many backup tools, network management utilities, CI/CD agents, IoT firmware stacks, and NAS appliances ship their own statically-linked copy of it.

The vulnerability lives in ssh2_transport_read() inside transport.c, the function responsible for parsing incoming SSH transport packets during the initial handshake. The SSH binary packet protocol prefixes every message with a 32-bit packet_length field declaring how many bytes follow. The receiver is supposed to allocate a buffer of that size, then read the rest of the packet into it.

The Root Cause: Unchecked Integer Overflow

libssh2 through version 1.11.1 reads the attacker-controlled packet_length from the wire and passes it into an arithmetic expression before validating it. The expression looks roughly like this:

total_num = 4;
total_num += packet_length + mac_len + auth_len;

if (total_num > 35000 || total_num == 0)
    reject;

allocate(total_num);

An attacker sets:

packet_length = 0xffffffff
mac_len       = 0
auth_len      = 16

The addition wraps on 32-bit arithmetic (and can wrap on 64-bit platforms too, since the operands are evaluated as 32-bit integers before widening to size_t):

0xffffffff + 0 + 16 = 0x0000000f = 15  (modulo 2^32)
total_num = 4 + 15 = 19 bytes allocated

The bound check (total_num > 35000) passes because 19 is well within limits. A tiny 19-byte buffer is allocated. But the original packet_length of 0xffffffff is still held in state. Subsequent packet-processing code that derives sizes from packet_length writes far beyond the 19-byte allocation — a classic integer-overflow-to-heap-overflow.

Attack Vector

An attacker needs to:

  1. Control or impersonate an SSH server that a libssh2 client will connect to.
  2. Complete enough of the SSH handshake for the transport layer to be active.
  3. Send a crafted packet with packet_length = 0xffffffff.

The overflow triggers during the pre-authentication handshake phase. No credentials. No user interaction. No prior access to the client machine. Exploitation conditions depend on heap layout, allocator behavior, and binary hardening — but the bug class (unchecked heap write from attacker-controlled length) is well-understood, and a PoC already exists on GitHub.

Real-World Impact

No confirmed in-the-wild exploitation had been reported as of the advisory date (June 30, 2026), and the CVE is not yet in CISA's Known Exploited Vulnerabilities (KEV) catalog. However:

  • Public PoC code is available.
  • The vulnerability was disclosed June 17–29, 2026, giving attackers ample time to weaponize it.
  • The 30-day EPSS exploitation probability is expected to climb sharply as researchers publish further analysis.
  • The supply-chain spread (curl, PHP, Git GUIs, IoT firmware) means the blast radius extends far beyond organizations that maintain libssh2 directly.

A companion vulnerability, CVE-2026-55199 (CVSS 8.2, High), was disclosed in the same batch. It causes a denial-of-service via a malicious server-advertised extension count that sends the client into a near-infinite loop during key exchange. This is not code execution but can effectively hang any automated SSH workflow that connects to a compromised server.


2. Who Is Affected?

Directly vulnerable: Any software dynamically or statically linking libssh2 ≤ 1.11.1 that makes outbound SSH/SCP/SFTP connections.

Specific high-risk categories:

  • curl with SCP/SFTP support (--libcurl or distributions that bundle libssh2)
  • PHP with ssh2_* or SFTP functions (ext-ssh2)
  • Git GUI clients that use libssh2 for SSH remotes
  • Backup agents using rsync-over-SSH, SFTP backup destinations
  • CI/CD pipeline agents that pull/push over SCP or SFTP
  • Network appliances, NAS devices, routers with bundled SSH management clients
  • IoT/embedded firmware that uses libssh2 for telemetry or update delivery

Exposure is higher when:

  • Host key verification is disabled (StrictHostKeyChecking no equivalents)
  • Clients connect to external or third-party SSH servers
  • Software statically links libssh2 (no OS-level package update covers it)
  • Devices receive infrequent or no firmware updates

Exposure is lower (but not zero) when:

  • All SSH traffic is restricted to internal, fully trusted servers
  • Host keys are rigorously pinned and verified before connection

3. How to Detect It (Testing)

Manual Testing Steps

Step 1: Inventory libssh2 usage on Linux hosts

# Find dynamically linked binaries
find /usr /bin /sbin /opt -type f -executable \
  -exec ldd {} 2>/dev/null \; | grep libssh2

# Check installed package version
dpkg -l libssh2-1          # Debian/Ubuntu
rpm -q libssh2             # RHEL/CentOS/Fedora

Step 2: Find statically linked copies

# Search for libssh2 version strings in binaries
find /usr /opt -type f -executable | \
  xargs strings 2>/dev/null | grep -i "libssh2/"

# Also check container images you build or run
docker inspect <image> | grep -i ssh

Step 3: Confirm version is vulnerable

Look for version strings 1.11.1 or earlier, or confirm that the build does NOT include commit 97acf3df from the libssh2 GitHub repository.

# If building from source, verify the fix is present
cd libssh2
git log --oneline src/transport.c | head -5
# Look for the commit that adds LIBSSH2_PACKET_MAXPAYLOAD boundary check

Step 4: Indicators from CVE-2026-55199 (DoS companion)

Watch for SSH client processes entering CPU-spin states (near 100% CPU) during connection attempts, particularly when connecting to external hosts.

Automated Scanning

Trivy (container/dependency scanning):

trivy fs --scanners vuln /path/to/project
trivy image your-image:tag
# Look for: CVE-2026-55200 in libssh2 findings

Grype:

grype dir:/path/to/project
grype your-image:tag
# Will flag libssh2 <= 1.11.1

OSS-Fuzz / Snyk (for source dependencies):

snyk test --all-projects
# Catches libssh2 if listed as a dependency in supported manifests

Nessus / Qualys:

  • Both vendors have released plugins for CVE-2026-55200 targeting libssh2 package versions on Linux distributions.

Code Review Checklist

  • Does the application link libssh2? Check CMakeLists.txt, configure.ac, vcpkg.json, conanfile.txt
  • Is libssh2 statically linked? If yes, a package update alone will NOT fix it — requires rebuild
  • Does the application disable host key verification? (LIBSSH2_HOSTKEY_POLICY_TOFU, known_hosts bypass)
  • Does the application connect to externally-controlled SSH endpoints?
  • Does any Docker image bundle its own libssh2 rather than using system packages?

4. How to Fix It (Mitigation)

Step-by-Step Remediation

1. Update libssh2 via your package manager (fastest path for most systems)

# Debian / Ubuntu (patched build in testing as 1.11.1-4)
sudo apt update && sudo apt upgrade libssh2-1 libssh2-1-dev

# RHEL / CentOS / Fedora
sudo dnf update libssh2

# Alpine
apk upgrade libssh2

# macOS (Homebrew)
brew upgrade libssh2

Verify the fix is present after updating — the patched source includes a LIBSSH2_PACKET_MAXPAYLOAD boundary check in ssh2_transport_read().

2. Build from source if a patched package isn't available yet

git clone https://github.com/libssh2/libssh2.git
cd libssh2
# Verify the critical fix commit is present
git log --oneline | grep -i "packet_length\|MAXPAYLOAD\|97acf3d"

# Build
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
sudo make install

The minimum required commits are:

  • 97acf3df — fixes CVE-2026-55200 (RCE)
  • 1762685 — fixes CVE-2026-55199 (DoS)

3. Rebuild statically-linked applications

If curl, PHP, or another application in your environment statically links libssh2, updating the system package does NOT fix those binaries. They must be recompiled against the patched libssh2 source.

# Example: rebuild curl with patched libssh2
./configure --with-libssh2=/path/to/patched/libssh2
make && make install

4. Update container base images and rebuild

# In your Dockerfile, force a package refresh
RUN apt-get update && apt-get upgrade -y libssh2-1 && apt-get clean

Then rebuild and redeploy affected images.

5. Contact vendors for firmware/appliance patches

For embedded devices, NAS units, network appliances — open tickets with vendors and monitor their security advisories. Reference CVE-2026-55200 explicitly.

Code Fix Example

Vulnerable pattern (libssh2 ≤ 1.11.1):

/* transport.c — ssh2_transport_read() */
total_num += packet_length + mac_len + auth_len;

if (total_num > 35000 || total_num == 0) {
    /* check too late — overflow already happened */
    return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
}

Fixed pattern (post-patch):

/* Validate BEFORE arithmetic — fix from commit 97acf3df */
if (packet_length > LIBSSH2_PACKET_MAXPAYLOAD) {
    return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
}

total_num += packet_length + mac_len + auth_len;

Configuration Hardening

While patches are applied, reduce attack surface:

# Firewall: restrict outbound SSH/SCP/SFTP to known-good IPs only
iptables -A OUTPUT -p tcp --dport 22 -d <trusted_ip_range> -j ACCEPT
iptables -A OUTPUT -p tcp --dport 22 -j DROP

# Enforce SSH host key verification in scripts/tools that use libssh2
# Example for curl:
curl --hostpubmd5 <expected_md5> sftp://host/path/file
# Or use known_hosts pinning in your application's libssh2 setup

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: Connect to a known-good SSH server with patched libssh2 — verify normal operation.
  • Scenario B: Use the public arithmetic verifier PoC to confirm the vulnerable allocation path is rejected.
  • Scenario C: Confirm that application-level SSH/SCP/SFTP functionality remains intact after patching or rebuilding.

Security Test Cases

Test Case 1: Verify boundary check is enforced

  • Precondition: Apply patch (commit 97acf3df included in build)
  • Steps: Build and run the public arithmetic verifier (cve_2026_55200_probe)
  • Expected Result:
    fixed32_decision=rejected: out of boundary
    
    The patched code path rejects the malicious 0xffffffff packet length before allocation.

Test Case 2: Confirm installed package version

# Debian/Ubuntu
dpkg -l libssh2-1 | grep libssh2
# Expected: version shows patched build (e.g., 1.11.1-4 or later)

# Check the library directly for the fix
strings /usr/lib/x86_64-linux-gnu/libssh2.so.1 | grep -i "MAXPAYLOAD\|out_of_boundary"

Test Case 3: Verify statically-linked binaries

# Check curl binary specifically
curl --version | grep -i ssh
strings $(which curl) | grep "libssh2\|1\.11\."
# Confirm version reflects patched build if statically linked

Automated Tests

#!/usr/bin/env python3
"""
Quick validation that libssh2 packet_length arithmetic no longer wraps.
Run after updating/rebuilding to confirm CVE-2026-55200 fix.
"""

import ctypes
import subprocess
import sys

def check_libssh2_version():
    result = subprocess.run(
        ["pkg-config", "--modversion", "libssh2"],
        capture_output=True, text=True
    )
    if result.returncode != 0:
        print("WARNING: pkg-config could not find libssh2")
        return None
    version = result.stdout.strip()
    print(f"[*] libssh2 version: {version}")
    return version

def verify_arithmetic_fix():
    """
    Confirm the vulnerable 32-bit overflow no longer produces a tiny allocation.
    The fix should reject packet_length=0xffffffff before arithmetic.
    """
    packet_length = 0xffffffff
    mac_len = 0
    auth_len = 16
    MAXPAYLOAD = 35000  # RFC 4253 ceiling

    # Simulate patched check
    if packet_length > MAXPAYLOAD:
        print("[PASS] Patched boundary check correctly rejects oversized packet_length")
        return True
    else:
        print("[FAIL] Boundary check is missing — still vulnerable!")
        return False

if __name__ == "__main__":
    version = check_libssh2_version()
    result = verify_arithmetic_fix()
    sys.exit(0 if result else 1)

6. Prevention & Hardening

Best Practices

Practice 1: Software Composition Analysis (SCA) in CI/CD

Add dependency scanning to every pipeline so CVEs in transitive dependencies like libssh2 are caught before deployment, not after disclosure.

# Example GitHub Actions step
- name: Scan for CVEs
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    exit-code: '1'
    severity: 'CRITICAL,HIGH'

Practice 2: Enforce SSH host key verification everywhere

The entire attack requires a client to connect to a malicious server. Host key pinning makes that far harder.

  • Never use StrictHostKeyChecking no in automation.
  • Pin expected host key fingerprints in known_hosts files.
  • For libssh2 programmatically: use libssh2_knownhost_checkp() and fail-closed on unknown hosts.

Practice 3: Maintain a live SBOM (Software Bill of Materials)

Know where libssh2 is in your environment — dynamically linked packages, statically compiled binaries, container images, and vendor appliances. A gap in that inventory is a gap in your patch coverage.

Practice 4: Network egress controls for SSH clients

Automated jobs should only be allowed to reach the specific SSH endpoints they need. Block all other outbound port 22 traffic via firewall policy.

# Allow only specific backup destination
iptables -A OUTPUT -p tcp --dport 22 -d 10.0.0.50 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 22 -j DROP

Monitoring & Detection

Since CVE-2026-55200 is a client-side vulnerability triggered by connecting to a hostile server, detection focuses on anomalous connection behavior and crash signals:

# Watch for SSH client crashes (SIGSEGV from heap corruption)
journalctl -f | grep -E "segfault|SIGSEGV|heap|ssh|curl|sftp"

# Monitor for unexpected SSH outbound connections
tcpdump -i eth0 'tcp port 22' -nn | awk '{print $3}' | sort -u

# Alert on libssh2 process crash dumps
find /var/crash /tmp /var/core -name "core.*" -newer /tmp/.last_check \
  -exec file {} \; | grep -i ssh

For SIEM/EDR environments, alert on:

  • Processes linking libssh2 that terminate abnormally during connection establishment
  • Outbound SSH connections to IP addresses not in an approved allowlist
  • Unusually high CPU usage from SSH-related processes (indicator of CVE-2026-55199 DoS)

References

Latest from the blog

See all →