HexBrain

curl · Report #3761647

TLS conn reuse and session cache ignore fsslctx callback and ssl_config_data flags ( incomplete fix variant of 7541ae569 )

Informativestatus
highseverity
16upvotes
1 Jun 2026disclosed

How it was found

Summary

match_ssl_primary_config() in lib/vtls/vtls.c:194 and the session-cache key built by cf_ssl_peer_key_build() in lib/vtls/vtls_scache.c:240 both compare only struct ssl_primary_config fields when deciding whether to reuse a TLS connection or cached session. Several fields that materially change the TLS handshake outcome live in struct ssl_config_data instead and are not compared. When two easy handles share a connection pool (curl_share or curl_multi) and target the same host but differ on any of these fields, the second handle silently inherits the first handle's TLS session - including any security weakening applied by CURLOPT_SSL_CTX_FUNCTION.

This is an incomplete-fix variant of commit 7541ae569 (PR #21667, Joshua Rogers / AISLE Research, merged 2026-05-19), which moved cert, cert_type, key, key_type, key_passwd, and cert_blob/key_blob from ssl_config_data into ssl_primary_config for exactly this reason. The following fields were missed and remain in ssl_config_data:

  • fsslctx / fsslctxp (CURLOPT_SSL_CTX_FUNCTION / CURLOPT_SSL_CTX_DATA) - application callback that can arbitrarily modify SSL_CTX (disable verification, install custom cert-verify callback, change cipher suite)
  • auto_client_cert (CURLSSLOPT_AUTO_CLIENT_CERT, Schannel) - automatic client certificate selection
  • earlydata (TLS 1.3 0-RTT) - enables early data which changes replay-attack surface
  • no_revoke / revoke_best_effort - certificate revocation check flags
  • no_partialchain - chain verification strictness
  • native_ca_store - OS certificate store usage

Affected version

Tested on libcurl HEAD 230a98663 (origin/master, 2026-05-26) with OpenSSL backend. The bug exists in all versions since CURLOPT_SSL_CTX_FUNCTION was introduced, but became more impactful after PR #21667 (2026-05-19) explicitly established the pattern of moving security-relevant fields into ssl_primary_config for comparison - and missed these.

curl 8.14.0-DEV (x86_64-pc-linux-gnu) libcurl/8.14.0-DEV OpenSSL/3.5.0

Steps To Reproduce

  1. Start a self-signed HTTPS server (not in the host CA bundle):
openssl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem \
    -days 1 -nodes -subj "/CN=poc.local"
openssl s_server -cert c.pem -key k.pem -accept 14443 -WWW &
echo "PoC OK" > /tmp/poc_index.html
  1. Compile the attached poc.c:
gcc -o poc poc.c -lcurl -lssl
  1. Run the PoC:
./poc https://localhost:14443/
  1. Observed output (BUGGY - current upstream):
[A: CB-installed   ] rc= 0 (No error) http=200
[B: no-CB          ] rc= 0 (No error) http=200

Handle A installs SSL_CTX_set_cert_verify_callback() via CURLOPT_SSL_CTX_FUNCTION that approves every certificate chain. Handle B has NO callback and CURLOPT_SSL_VERIFYPEER=1 (strict). Both share a curl_share with CURL_LOCK_DATA_CONNECT and CURL_LOCK_DATA_SSL_SESSION. B silently reuses A's TLS session and inherits A's loosened verification.

  1. Control test (without share - B correctly fails):
# Compile and run the control (no curl_share):
gcc -o poc_noshare poc_noshare.c -lcurl -lssl
./poc_noshare https://localhost:14443/

Output:

[A] rc=0 (No error) http=200
[B] rc=60 (SSL peer certificate or SSH remote key was not OK) http=0

Without the shared pool, B correctly rejects the self-signed certificate. This differential proves the connection/session reuse is the bypass path.

Again: there is no bug-bounty for curl. We do not offer any rewards, only proper credits.

Understood. Submitting for CVE assignment and credit.

Suggested fix

Move the missing fields into ssl_primary_config (same pattern as PR #21667), or add explicit comparisons in match_ssl_primary_config() and include them in the session-cache key:

// lib/vtls/vtls.c match_ssl_primary_config() -- add:
if(data->ssl_config.fsslctx != needle->ssl_config.fsslctx)
    return FALSE;
if(data->ssl_config.fsslctxp != needle->ssl_config.fsslctxp)
    return FALSE;
if(data->ssl_config.auto_client_cert != needle->ssl_config.auto_client_cert)
    return FALSE;
if(data->ssl_config.earlydata != needle->ssl_config.earlydata)
    return FALSE;
if(data->ssl_config.no_revoke != needle->ssl_config.no_revoke)
    return FALSE;
if(data->ssl_config.revoke_best_effort != needle->ssl_config.revoke_best_effort)
    return FALSE;
if(data->ssl_config.no_partialchain != needle->ssl_config.no_partialchain)
    return FALSE;
if(data->ssl_config.native_ca_store != needle->ssl_config.native_ca_store)
    return FALSE;

// lib/vtls/vtls_scache.c cf_ssl_peer_key_build() -- include in key hash:
// Append fsslctx pointer value + flags to the key string.

Impact

Summary

An application that uses curl_share or curl_multi to pool TLS connections between easy handles with different SSL configurations will silently inherit the first handle's TLS security posture on subsequent handles. Specifically:

  1. TLS verification bypass - Handle A installs CURLOPT_SSL_CTX_FUNCTION that disables certificate verification. Handle B connects to the same host with strict CURLOPT_SSL_VERIFYPEER=1 but no callback. B silently reuses A's session and connects without verification - a MitM attacker can intercept B's traffic with a forged certificate.

  2. Client certificate confusion (Schannel) - Handle A enables CURLSSLOPT_AUTO_CLIENT_CERT for automatic client certificate selection. Handle B does not. B may unintentionally present A's client certificate to the server, authenticating as the wrong identity.

  3. Revocation check bypass - Handle A sets no_revoke=1. Handle B expects revocation checking. B inherits A's session and skips revocation checks, potentially trusting a revoked certificate.

This is the same class of bug as 7541ae569 (PR #21667, AISLE Research, 2026-05-19) which moved cert/key fields into ssl_primary_config for comparison. The fields listed above were missed in that fix.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original