From bd10924b47c8bf94127eaee8c56e7b5e5418da46 Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 16 Jun 2026 10:12:24 +0200 Subject: [PATCH] url: connection credentials origin When tying credentials to a connection (NTLM, Negotiate) also link the origin the credentials are for. This prevents a connection reuse with the same credentials, but intended for another origin. The mis-reuse could happen for a forwarding proxy and NTLM (although, in the mind of the person writing this, it is an insane setup). Closes #22040 --- lib/http_negotiate.c | 9 ++++++++- lib/http_ntlm.c | 8 ++++++++ lib/url.c | 24 +++++++++++------------- lib/urldata.h | 1 + 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c index 4aa67f01d5..891369b5bc 100644 --- a/lib/http_negotiate.c +++ b/lib/http_negotiate.c @@ -42,6 +42,7 @@ static void http_auth_nego_reset(struct connectdata *conn, conn->proxy_negotiate_state = GSS_AUTHNONE; else { conn->http_negotiate_state = GSS_AUTHNONE; + Curl_peer_unlink(&conn->creds_origin); Curl_creds_unlink(&conn->creds); } if(neg_ctx) @@ -132,13 +133,19 @@ CURLcode Curl_input_negotiate(struct Curl_easy *data, struct connectdata *conn, if(result) http_auth_nego_reset(conn, neg_ctx, proxy); - if(!proxy) { + if(!result && !proxy) { /* Start it up. From this time onwards, the connection is tied * tp the credentials used. */ + if(conn->creds_origin && + !Curl_peer_equal(conn->creds_origin, data->state.origin)) { + DEBUGASSERT(0); /* should not happen. */ + return CURLE_FAILED_INIT; + } if(conn->creds && !Curl_creds_same(creds, conn->creds)) { DEBUGASSERT(0); /* should not happen. */ return CURLE_FAILED_INIT; } + Curl_peer_link(&conn->creds_origin, data->state.origin); Curl_creds_link(&conn->creds, creds); } diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c index 1442fd6f7a..dc9911fdac 100644 --- a/lib/http_ntlm.c +++ b/lib/http_ntlm.c @@ -93,6 +93,8 @@ CURLcode Curl_input_ntlm(struct Curl_easy *data, else if(*state == NTLMSTATE_TYPE3) { infof(data, "NTLM handshake rejected"); Curl_auth_ntlm_remove(conn, proxy); + Curl_peer_unlink(&conn->creds_origin); + Curl_creds_unlink(&conn->creds); *state = NTLMSTATE_NONE; return CURLE_REMOTE_ACCESS_DENIED; } @@ -184,10 +186,16 @@ CURLcode Curl_output_ntlm(struct Curl_easy *data, bool proxy) if(!proxy) { /* Start it up. From this time onwards, the connection is tied * tp the credentials used. */ + if(conn->creds_origin && + !Curl_peer_equal(conn->creds_origin, data->state.origin)) { + DEBUGASSERT(0); /* should not happen. */ + return CURLE_FAILED_INIT; + } if(conn->creds && !Curl_creds_same(creds, conn->creds)) { DEBUGASSERT(0); /* should not happen. */ return CURLE_FAILED_INIT; } + Curl_peer_link(&conn->creds_origin, data->state.origin); Curl_creds_link(&conn->creds, creds); } result = Curl_auth_create_ntlm_type1_message(data, creds, "HTTP", diff --git a/lib/url.c b/lib/url.c index 8da2aca924..3018dc438b 100644 --- a/lib/url.c +++ b/lib/url.c @@ -515,6 +515,7 @@ void Curl_conn_free(struct Curl_easy *data, struct connectdata *conn) Curl_creds_unlink(&conn->socks_proxy.creds); #endif Curl_creds_unlink(&conn->creds); + Curl_peer_unlink(&conn->creds_origin); curlx_safefree(conn->options); curlx_safefree(conn->localdev); Curl_ssl_conn_config_cleanup(conn); @@ -1011,18 +1012,11 @@ static bool url_match_auth_ntlm(struct connectdata *conn, possible. (Especially we must not reuse the same connection if partway through a handshake!) */ if(m->want_ntlm_http) { - if(!Curl_creds_same(m->data->state.creds, conn->creds)) { - /* we prefer a credential match, but this is at least a connection - that can be reused and "upgraded" to NTLM if it does - not have any auth ongoing. */ -#ifdef USE_SPNEGO - if((conn->http_ntlm_state == NTLMSTATE_NONE) && - (conn->http_negotiate_state == GSS_AUTHNONE)) { -#else - if(conn->http_ntlm_state == NTLMSTATE_NONE) { -#endif - m->found = conn; - } + if(conn->creds && + (!Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) { + /* connection credentials in play and not the same or not for the + * same origin. */ return FALSE; } } @@ -1079,7 +1073,9 @@ static bool url_match_auth_nego(struct connectdata *conn, already authenticating with the right credentials. If not, keep looking so that we can reuse Negotiate connections if possible. */ if(m->want_nego_http) { - if(!Curl_creds_same(m->needle->creds, conn->creds)) + if(conn->creds && + (!Curl_creds_same(conn->creds, m->data->state.creds) || + !Curl_peer_equal(conn->creds_origin, m->data->state.origin))) return FALSE; } else if(conn->http_negotiate_state != GSS_AUTHNONE) { @@ -1816,6 +1812,7 @@ static CURLcode url_set_conn_login(struct Curl_easy *data, { /* If our protocol needs a password and we have none, use the defaults */ if((conn->scheme->flags & PROTOPT_NEEDSPWD) && !conn->creds) { + Curl_peer_link(&conn->creds_origin, data->state.origin); if(data->state.creds) Curl_creds_link(&conn->creds, data->state.creds); else @@ -1825,6 +1822,7 @@ static CURLcode url_set_conn_login(struct Curl_easy *data, else if(!(conn->scheme->flags & PROTOPT_CREDSPERREQUEST)) { /* for protocols that do not handle credentials per request, * the connection credentials are set by the initial transfer. */ + Curl_peer_link(&conn->creds_origin, data->state.origin); Curl_creds_link(&conn->creds, data->state.creds); } diff --git a/lib/urldata.h b/lib/urldata.h index a0d7fa421d..232364fcf3 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -301,6 +301,7 @@ struct connectdata { struct proxy_info http_proxy; #endif struct Curl_creds *creds; /* When connection itself is tied to credentials */ + struct Curl_peer *creds_origin; /* origin tied credentials are for */ char *options; /* options string, allocated */ struct curltime created; /* creation time */ struct curltime lastused; /* when returned to the connection pool as idle */