Inside CVE-2026-55200: How a Missing Bounds Check in libssh2 Becomes Remote Code Execution
A malicious SSH server can corrupt heap memory on any client using libssh2, before authentication and without any user interaction. This blog walks through the exact arithmetic behind the bug, why it lives in ssh2_transport_read(), how the exploitation path works conceptually, and what to check in your own environment.
Summary
SSH has long been trusted to securely access remote systems, but what if the server you're connecting to is the attacker? The disclosure of CVE-2026-55200 and CVE-2026-55199 in libssh2, a client-side library that implements the SSH2 protocol, demonstrates exactly that risk. Together, these vulnerabilities let a malicious SSH server trigger a client crash before authentication and, under certain conditions, potentially achieve remote code execution. The release of a public proof-of-concept, in a GitHub archive titled "exploitarium" on June 29, 2026, has further increased the urgency for organizations to patch affected systems.
This is not a new bug class for libssh2. A near-identical integer overflow in the same function was fixed in 2019 as CVE-2019-3855. The recurrence, seven years later, is a useful case study in how easily an unchecked length field creeps back into transport layer parsing code after refactors.
What libssh2 is, and what it is not
libssh2 implements the client side of SSH2. It is embedded, often statically, inside curl builds with
SCP/SFTP support, Git GUI clients, PHP's ssh2 extension (common in hosting control panels and
deployment scripts), backup agents, and network appliances that automate outbound SSH connections. Static
linking is the operational sting in this disclosure: a distribution patching its system libssh2
package does nothing for a tool that compiled its own copy in and never touches the system library again.
libssh2 versus libssh
These are two separate, unrelated projects that share a confusingly similar name.
| libssh2 | libssh | |
|---|---|---|
| Role | Client-only library | Client and server library |
| Typical embedder | curl, Git, PHP, backup and automation tools | Standalone SSH servers, some tunnel and VPN software |
| Relation to this CVE | Affected | Not affected, unrelated codebase |
Vulnerability Details
| CVE ID | CVSS Score | EPSS Score | Affected Products | Vulnerability Type |
|---|---|---|---|---|
| CVE-2026-55200 | 8.3 (High) | 0.73% | libssh2 v1.11.1 and below | Heap-based Buffer Overflow |
| CVE-2026-55199 | 7.5 (High) | 0.41% | libssh2 v1.11.1 and below | Infinite Loop / Resource Exhaustion |
Root Cause Analysis
The bug sits in ssh2_transport_read(), the function responsible for parsing raw SSH packets off
the wire. During the unauthenticated part of the handshake, a malicious server can send a packet with a
crafted packet_length field. Because that field is not validated against an upper bound before
it is used in size calculations, the arithmetic wraps around, producing an undersized memory allocation that
later packet processing code still treats as if it were the original, much larger size. The result is an
out-of-bounds heap write, and depending on allocator behavior and binary layout, a path to remote code
execution.
The SSH packet header and the trust boundary
Before any encryption or authentication is established, an SSH client and server exchange packets in a fixed binary format defined by RFC 4253. Every packet begins with the same header:
| Field | Size | Notes |
|---|---|---|
| packet_length | 4 bytes | Attacker controlled during the unauthenticated handshake |
| padding_length | 1 byte | Number of padding bytes appended |
| payload | variable | The actual message content |
| padding | variable | Random padding bytes |
| MAC | optional | Not present until encryption is active |
packet_length is a plain 32-bit unsigned integer, and it is the very first piece of information
the client reads for a given packet. Nothing upstream has validated it yet, because nothing upstream exists
at that point in the handshake. A correct implementation has to treat that field as hostile and check it
against a sane maximum, RFC 4253 specifies 35,000 bytes, before doing any arithmetic with it. CVE-2026-55200
exists because that check either ran too late in ssh2_transport_read(), or did not run at all
on the code path a crafted packet could reach.
The wraparound arithmetic
The underlying bug pattern is straightforward integer overflow. Somewhere in the allocation path, a fixed
amount of struct or header overhead is added to the attacker-supplied packet_length to compute
how large a buffer to allocate. That addition happens in a 32-bit unsigned integer, which can only represent
values from 0 up to 4,294,967,295 (2 to the power of 32, minus 1). Add anything past that ceiling and the
value does not raise an error. It silently wraps back around to a small number, because unsigned integer
overflow is defined, well-behaved arithmetic in C, not a fault the language will catch for you.
Try clicking the buttons above. When packet_length is small or even really large, the math just works normally, everything adds up fine. But push it to 4,294,967,267, and the total lands exactly on 4,294,967,295, the biggest number a 32-bit field can hold. Add even one byte more, and something strange happens: instead of getting a bigger number, the total suddenly drops all the way down to 12.
That's the bug. The computer tried to store a number too big to fit, so it "wrapped around" back to a tiny value. Now, code further down thinks the incoming data is only 12 bytes long and sets aside a tiny buffer for it, even though the attacker is actually about to send billions of bytes. Whatever handles that data has no idea it was given the wrong size, and that mismatch is where things go wrong.
Inside ssh2_transport_read()
ssh2_transport_read() in src/transport.c is the function responsible for turning
raw bytes off the socket into a parsed SSH packet. It runs for the life of the connection, but the
interesting window is the handshake, before encryption keys exist, because that is the only point where the
client is guaranteed to process attacker-supplied data with no prior verification. Conceptually, the
function:
- 1. Reads the 4-byte
packet_lengthfield from the socket. - 2. Computes how large a buffer is needed to hold the incoming payload, padding, and any bookkeeping overhead.
- 3. Allocates or reuses a buffer sized for that computation.
- 4. Reads the payload, padding, and MAC into that buffer.
- 5. Passes the parsed packet up to the key exchange or transport layer.
The flaw is that step 2 and step 4 disagree after a wraparound. Step 2 computes a buffer size using the
wrapped, tiny value. Step 4 still has the original, oversized packet_length in scope and uses
it, or a value derived from it, to decide how many bytes to copy. Neither line of code is incorrect by
itself. The vulnerability lives entirely in the missing comparison that should have rejected an oversized
packet_length before either step ran.
The official fix, in commit 97acf3d, adds exactly that comparison: reject any
packet_length above the protocol's permitted maximum before it participates in any arithmetic
or allocation sizing.

How CVE-2026-55200 Can Be Exploited
Attack prerequisites
- • A target application linked against a vulnerable version of libssh2 (1.11.1 or earlier).
- • That application initiates an outbound SSH connection to a server the attacker controls, or the attacker can intercept the TCP connection before the legitimate server responds.
- • No valid credentials are needed. No user interaction beyond the application making its normal connection is needed.
Conceptual exploitation flow
- 1. The client application calls into libssh2 to begin a handshake with the target server, typically via a
function like
libssh2_session_handshake(). - 2. The attacker's server responds during the unauthenticated key exchange phase with a crafted packet whose
packet_lengthheader is set to a value near the top of the 32-bit range. - 3.
ssh2_transport_read()computes an allocation size using that value plus fixed overhead, and the addition wraps around to a small number. - 4. A small buffer is allocated based on the wrapped value.
- 5. Later logic in the same read path still operates using the original, oversized
packet_length, and writes payload data into the undersized buffer past its real boundary. - 6. The out-of-bounds write corrupts adjacent heap memory. Whether this is exploitable for code execution, rather than just a crash, depends on the target binary, the allocator implementation, active mitigations such as ASLR and heap hardening, and what memory is adjacent to the corrupted buffer.
Reliable remote code execution is not guaranteed just because the memory corruption primitive exists. It depends heavily on allocator behavior, compiler mitigations, and how the embedding application links and uses libssh2. Treat this as a critical memory safety bug with a credible path to RCE, not as a universal, drop-in remote exploit.
Lab Validation — Confirmed RCE in an Isolated Sandbox
root@a52aa74b44d1:/app/cve-2026-55200# gcc -O0 -g -Wall -Wextra -o poc/libpwn_local_rce_harness poc/libpwn_local_rce_harness.c root@a52aa74b44d1:/app/cve-2026-55200# python3 poc/libpwn_local_rce_exploit.py --harness ./poc/libpwn_local_rce_harness --proof ./poc/libpwn_rce_proof.txt LEAK exec_callback=0x5a32ad8f41d3 callback_offset=24 command_offset=32 ptr_size=8 accepted packet_length=0xffffffff allocation=19 copy_len=117 body_len=117 exec_callback command=/bin/sh -c 'echo libpwn-rce-verified > /app/cve-2026-55200/poc/libpwn_rce_proof.txt' system_rc=0 process_rc=0 payload_len=117 proof_path=/app/cve-2026-55200/poc/libpwn_rce_proof.txt RCE_PROOF=PASS root@a52aa74b44d1:/app/cve-2026-55200# cat poc/libpwn_rce_proof.txt libpwn-rce-verified
Impact
The direct technical impact is heap memory corruption reachable by an unauthenticated remote server, with a credible path to arbitrary code execution in the context of any process linking the vulnerable libssh2 code. The broader impact comes from how quietly libssh2 spreads through the software supply chain.
| Category | Examples | Why it matters here |
|---|---|---|
| Download and transfer tools | curl builds with SCP/SFTP support | Extremely widely deployed, often in scripts and automation that connect to many different hosts |
| Developer tooling | Git GUI clients, some Git over SSH integrations | Developer workstations connecting to a wide range of remote repositories |
| Web hosting and PHP | PHP's ssh2 extension | Common in hosting control panels and deployment scripts that connect outbound to customer or third party servers |
| Automation and infrastructure | Backup agents, CI/CD runners, firmware updaters | Frequently make unattended outbound SSH connections with no human watching the session |
| Embedded and appliances | Network appliances, IoT firmware | Long patch cycles, static linking, and low visibility into what libraries are actually bundled |
MITRE ATT&CK Mapping
| Technique ID | Technique Name | Tactic |
|---|---|---|
| T1190 | Exploit Public-Facing Application | Initial Access |
| T1078.001 | Default Accounts | Initial Access |
| T1027 | Obfuscated Files or Information | Defense Evasion |
| T1071.001 | Web Protocols | Command-And-Control |
| T1499 | Endpoint Denial of Service | Impact |
Mitigation
- 1. Patch to a build containing commit: There is no official tagged libssh2 release containing the fix at the time of writing. The patch exists on the mainline branch as commit
97acf3d, and distributions are backporting it independently.
Fix commit: 97acf3dfda80c91c3a8c9f2372546301d4a1a7a8
Upstream pull request: libssh2/libssh2#2052
- 2. Restrict outbound SSH connections to trusted, known servers only, especially for automation, CI/CD, backup, and embedded/IoT systems. Use firewall rules and network segmentation to limit which hosts your libssh2-based clients can reach.
- 3. Verify host keys rigorously and prioritize hardening for any client that reaches external SSH servers or resolves hosts via DNS names an attacker could potentially redirect.
Instantly Fix Risks with Saner Patch Management
Saner patch management is a continuous, automated, and integrated software that instantly fixes risks exploited in the wild. The software supports major operating systems like Windows, Linux, and macOS, as well as 550+ third-party applications.
It also allows you to set up a safe testing area to test patches before deploying them in a primary production environment. Saner patch management additionally supports a patch rollback feature in case of patch failure or a system malfunction.
Experience the fastest and most accurate patching software here.




