✓ProveIt
Examples / Report #3718195 Saved analysis

CVE-2026-8286: wrong STARTTLS connection reuse

curl · 2026-05-0720.8s analysis · $0.019 in model calls

What the report claims

A flaw in curl's STARTTLS connection reuse allows an attacker to reuse an unverified TLS session from a previous connection, bypassing verification and pinning checks.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-8286 wrong STARTTLS connection reuse

Shown for comparison after analysis. The pipeline did not see this label.

ProveIt's finding

Code supports the claim

The code supports the reported flaw. A maintainer should review its impact and decide what to do next.

Why this finding?
  • url_match_ssl_config at line 1028 only calls Curl_ssl_conn_config_match when needle->scheme->flags & PROTOPT_SSL
  • url_match_ssl_use at lines 833-838 permits reuse of an SSL-upgraded connection for schemes with PROTOPT_SSL_REUSE (STARTTLS protocols) without requiring PROTOPT_SSL
  • This creates a gap where STARTTLS connections are reused without comparing SSL configuration (verifypeer, pinned keys, etc.)
  • The flaw is the same class as CVE-2022-27782 but affects the STARTTLS path that the previous fix missed
  • Master at revision 455bebc2c7 still contains this logic gap
How this analysis ran 5 claims · 7 citations verified

Checked against curl permalink 455bebc2c7 (455bebc2c762). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.

1 · Grounding
grounded
no model · 9 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1656 tokens · $0.0002
3 · Evidence
4 code snippets
real curl source at 455bebc2c762 · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
11608 tokens · 13.7s · $0.0192
5 · Receipts
7 of 8 citations verified
every citation must point at code the judge was shown
Review a draft reply to the reporter

Edit and verify before sending.

Thank you for the detailed analysis. The code confirms the flaw: url_match_ssl_config (lib/url.c:1028) only invokes Curl_ssl_conn_config_match for PROTOPT_SSL schemes, while url_match_ssl_use (lib/url.c:833-838) allows STARTTLS schemes (with PROTOPT_SSL_REUSE) to reuse SSL-upgraded connections without that config check. This lets a connection with VERIFYPEER=0 be reused by one requiring VERIFYPEER=1 and pinning. We'll prepare a fix to extend the config comparison to the STARTTLS reuse path.

Inspect all claims and source code 5 claims · 4 code excerpts

Report claims

C1flaw supported

url_match_ssl_use() allows reuse of an SSL-upgraded connection via PROTOPT_SSL_REUSE without calling Curl_ssl_conn_config_match for imap://, pop3://, smtp://, ftp:// schemes with CURLOPT_USE_SSL=CURLUSESSL_ALL

Read the reasoning

For STARTTLS schemes (imap://, pop3://, smtp://, ftp://), the scheme lacks PROTOPT_SSL but has PROTOPT_SSL_REUSE. When an SSL-upgraded connection exists, url_match_ssl_use returns TRUE at line 838 (if protocol family matches), then url_match_ssl_config returns TRUE at line 1028 without calling Curl_ssl_conn_config_match because the PROTOPT_SSL gate fails.

C2location supported

url_match_ssl_use() at L833-838

Read the reasoning

The function url_match_ssl_use at lines 825-844 contains the cited logic at lines 833-838.

C3trigger not verifiable

imap:// scheme with CURLOPT_USE_SSL=CURLUSESSL_ALL and CURLOPT_SSL_VERIFYPEER=0

Read the reasoning

The provided code confirms CURLUSESSL_ALL exists, but scheme flags (PROTOPT_SSL vs PROTOPT_SSL_REUSE for imap://) are not shown in the provided snippets. The trigger is plausible given the logic but not directly verifiable from provided code alone.

lib/url.c:925
C4impact supported

An on-path attacker can MITM the STARTTLS upgrade and reuse the unverified TLS session from a previous connection

Read the reasoning

Since Curl_ssl_conn_config_match is not called for STARTTLS schemes, SSL parameters like CURLOPT_SSL_VERIFYPEER and pinned keys are not compared during connection reuse, allowing a weak session to be reused by a connection requiring strong verification.

C5reproduction supported

Connect handle A to imap://host with USE_SSL=CURLUSESSL_ALL and VERIFYPEER=0; then connect handle B to the same host with USE_SSL=CURLUSESSL_ALL and VERIFYPEER=1; B reuses A's unverified TLS session

Read the reasoning

The reproduction steps follow directly from the code path: handle A creates an SSL-upgraded connection with weak settings; handle B with strong settings matches the same connection via PROTOPT_SSL_REUSE and skips config comparison.

What the report says about other projects

not addressed The fix for CVE-2022-27782 added Curl_ssl_conn_config_match but only behind a PROTOPT_SSL gate, missing the STARTTLS path.

Read the original report

Full report on HackerOne ↗

Finding F3 from Andrew Nesbit's report: https://github.com/curl/curl/blob/455bebc2c7/lib/url.c#L1024-L1036 https://github.com/curl/curl/blob/455bebc2c7/lib/url.c#L825-L844 url_match_ssl_config() is gated on m->needle->scheme->flags & PROTOPT_SSL. For imap://, pop3://, smtp://, ftp://, ldap:// with CURLOPT_USE_SSL (STARTTLS), the scheme lacks PROTOPT_SSL, so Curl_ssl_conn_config_match is never called even though the connection runs TLS. url_match_ssl_use() at L833-838 lets a plain-scheme needle reuse an SSL-upgraded conn via PROTOPT_SSL_REUSE without ever reaching the config comparison; the req_tls check at L840 only fires when the existing conn is NOT ssl. ## Impact Impact: handle A connects imap://host with CURLOPT_USE_SSL=CURLUSESSL_ALL and CURLOPT_SSL_VERIFYPEER=0; on-path attacker MITMs the STARTTLS upgrade. Handle B connects imap://host with USE_SSL=ALL, VERIFYPEER=1, and a pinned key. B reuses A's unverified TLS session; B's verifypeer/pinning never run. (FTP's ftp_conns_match checks use_ssl level and ftp_ccc but not ssl_primary_config; IMAP/POP3/SMTP have no protocol-specific check at all.) Same bug class as CVE-2022-27782 (TLS/SSH too-eager reuse) - that fix added Curl_ssl_conn_config_match but only behind a PROTOPT_SSL gate, missing the STARTTLS path. Verified by reading both functions and tracing the needle for imap:// + USE_SSL through url_match_ssl_use → returns TRUE at L838, then url_match_ssl_config → returns TRUE at L1028 without calling Curl_ssl_conn_config_match.

Source code

lib/url.clines 999–1061 · lines 1024-1036 cited by the report
999 if(m->needle->scheme != conn->scheme) {
1000 /* `needle` and `conn` do not have the same scheme... */
1001 if(get_protocol_family(conn->scheme) != m->needle->scheme->protocol) {
1002 /* and `conn`s protocol family is not the protocol `needle` wants.
1003 * IMAPS would work for IMAP, but no vice versa. */
1004 return FALSE;
1005 }
1006 /* We are in an IMAPS vs IMAP like case. We expect `conn` to have SSL */
1007 if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
1008 DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T
1009 " has compatible protocol family, but no SSL, no match",
1010 conn->connection_id));
1011 return FALSE;
1012 }
1013 }
1014
1015 /* `needle` must have the same hostname and port in origin and
1016 * via_peer (if present, NULL peers are equal) */
1017 if(!Curl_peer_same_destination(m->needle->origin, conn->origin) ||
1018 !Curl_peer_same_destination(m->needle->via_peer, conn->via_peer))
1019 return FALSE;
1020 }
1021 return TRUE;
1022}
1023
1024static bool url_match_ssl_config(struct connectdata *conn,
1025 struct url_conn_match *m)
1026{
1027 /* If talking TLS, conn needs to use the same SSL options. */
1028 if((m->needle->scheme->flags & PROTOPT_SSL) &&
C1: url_match_ssl_config only calls Curl_ssl_conn_config_match when needle->scheme->flags & PROTOPT_SSL
C4: SSL config match skipped for non-PROTOPT_SSL schemes
C5: Config match skipped
1029 !Curl_ssl_conn_config_match(m->data, conn, FALSE)) {
1030 DEBUGF(infof(m->data, "Connection #%" FMT_OFF_T
1031 " has different SSL parameters, cannot reuse",
1032 conn->connection_id));
1033 return FALSE;
1034 }
1035 return TRUE;
1036}
1037
1038#ifdef USE_NTLM
1039static bool url_match_auth_ntlm(struct connectdata *conn,
1040 struct url_conn_match *m)
1041{
1042 /* If we are looking for an HTTP+NTLM connection, check if this is
1043 already authenticating with the right credentials. If not, keep
1044 looking so that we can reuse NTLM connections if
1045 possible. (Especially we must not reuse the same connection if
1046 partway through a handshake!) */
1047 if(m->want_ntlm_http) {
1048 if(Curl_timestrcmp(m->needle->user, conn->user) ||
1049 Curl_timestrcmp(m->needle->passwd, conn->passwd)) {
1050 /* we prefer a credential match, but this is at least a connection
1051 that can be reused and "upgraded" to NTLM if it does
1052 not have any auth ongoing. */
1053#ifdef USE_SPNEGO
1054 if((conn->http_ntlm_state == NTLMSTATE_NONE)
1055 && (conn->http_negotiate_state == GSS_AUTHNONE)) {
1056#else
1057 if(conn->http_ntlm_state == NTLMSTATE_NONE) {
1058#endif
1059 m->found = conn;
1060 }
1061 return FALSE;
lib/url.clines 800–869 · lines 825-844 cited by the report
800
801static bool url_match_multiplex_limits(struct connectdata *conn,
802 struct url_conn_match *m)
803{
804 if(CONN_INUSE(conn) && m->may_multiplex) {
805 DEBUGASSERT(conn->bits.multiplex);
806 /* If multiplexed, make sure we do not go over concurrency limit */
807 if(conn->attached_xfers >=
808 Curl_multi_max_concurrent_streams(m->data->multi)) {
809 infof(m->data, "client side MAX_CONCURRENT_STREAMS reached"
810 ", skip (%u)", conn->attached_xfers);
811 return FALSE;
812 }
813 if(conn->attached_xfers >=
814 Curl_conn_get_max_concurrent(m->data, conn, FIRSTSOCKET)) {
815 infof(m->data, "MAX_CONCURRENT_STREAMS reached, skip (%u)",
816 conn->attached_xfers);
817 return FALSE;
818 }
819 /* When not multiplexed, we have a match here! */
820 infof(m->data, "Multiplexed connection found");
821 }
822 return TRUE;
823}
824
825static bool url_match_ssl_use(struct connectdata *conn,
826 struct url_conn_match *m)
827{
828 if(m->needle->scheme->flags & PROTOPT_SSL) {
829 /* We are looking for SSL, if `conn` does not do it, not a match. */
830 if(!Curl_conn_is_ssl(conn, FIRSTSOCKET))
831 return FALSE;
832 }
833 else if(Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
C1: url_match_ssl_use allows reuse of SSL connection when needle lacks PROTOPT_SSL but has PROTOPT_SSL_REUSE and same protocol family
C2: url_match_ssl_use logic at lines 833-838 matches report
C5: Reuse path for SSL-upgraded connection with PROTOPT_SSL_REUSE
834 /* If the protocol does not allow reuse of SSL connections OR
835 is of another protocol family, not a match. */
836 if(!(m->needle->scheme->flags & PROTOPT_SSL_REUSE) ||
837 (get_protocol_family(conn->scheme) != m->needle->scheme->protocol))
838 return FALSE;
839 }
840 else if(m->req_tls)
841 /* a clear-text STARTTLS protocol with required TLS */
842 return FALSE;
843 return TRUE;
844}
845
846#ifndef CURL_DISABLE_PROXY
847static bool url_match_proxy_use(struct connectdata *conn,
848 struct url_conn_match *m)
849{
850 if(m->needle->bits.httpproxy != conn->bits.httpproxy ||
851 m->needle->bits.socksproxy != conn->bits.socksproxy)
852 return FALSE;
853
854 if(m->needle->bits.socksproxy &&
855 !proxy_info_matches(&m->needle->socks_proxy, &conn->socks_proxy))
856 return FALSE;
857
858 if(m->needle->bits.httpproxy) {
859 if(m->needle->bits.tunnel_proxy != conn->bits.tunnel_proxy)
860 return FALSE;
861
862 if(!proxy_info_matches(&m->needle->http_proxy, &conn->http_proxy))
863 return FALSE;
864
865 if(IS_HTTPS_PROXY(m->needle->http_proxy.proxytype)) {
866 /* https proxies come in different types, http/1.1, h2, ... */
867 if(m->needle->http_proxy.proxytype != conn->http_proxy.proxytype)
868 return FALSE;
869 /* match SSL config to proxy */
lib/vtls/vtls.clines 227–240 · definition of Curl_ssl_conn_config_match
227bool Curl_ssl_conn_config_match(struct Curl_easy *data,
C4: Curl_ssl_conn_config_match compares ssl_primary_config (verifypeer, pinned keys, etc.)
228 struct connectdata *candidate,
229 bool proxy)
230{
231#ifndef CURL_DISABLE_PROXY
232 if(proxy)
233 return match_ssl_primary_config(data, &data->set.proxy_ssl.primary,
234 &candidate->proxy_ssl_config);
235#else
236 (void)proxy;
237#endif
238 return match_ssl_primary_config(data, &data->set.ssl.primary,
239 &candidate->ssl_config);
240}
include/curl/curl.hlines 925–925 · definition of CURLUSESSL_ALL
Show 1 lines (not cited by the judge)
925#define CURLUSESSL_ALL 3L /* SSL for all communication or fail */