curl · Report #3923520
CVE-2026-19931: Negotiate ambient user conn reuse
How it was found
Summary
On Windows with SSPI/Negotiate, curl 8.21.0 can reuse a connection authenticated with ambient credentials for a later request using different explicit credentials.
In url_match_auth_nego(), the credential comparison is skipped when conn->creds == NULL. This allows a connection authenticated as one identity to be reused for a request configured with another identity. In my reproducer, request A authenticates as Administrator, request B supplies credentials for userB, but the server processes B as Administrator.
curl 8.20.0 does not reproduce this issue.
Affected version
Reproduced with libcurl 8.21.0 on Windows using SSPI/SPNEGO.
Root cause
In 8.21.0:
if(m->want_nego_http) {
if(conn->creds &&
(!Curl_creds_same(conn->creds, m->data->state.creds) ||
!Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
return FALSE;
}
The credential/origin comparison is only performed when conn->creds is non-NULL. Connections authenticated through ambient SSPI credentials can have conn->creds == NULL, so the check is skipped.
Steps To Reproduce
- Run the attached local Windows Negotiate server.
- Run the PoC as Windows user A (
Administratorin my test). - Request A uses
CURLAUTH_NEGOTIATEwithout explicit credentials. - Request B uses another easy handle on the same
CURLMwith explicit credentials foruserB. - On curl 8.21.0, request B reuses A's connection.
Observed:
8.20.0:
A default -> Administrator
B explicit userB -> userB
8.21.0:
A default -> Administrator
B explicit userB -> Administrator
Disabling connection reuse causes B to authenticate correctly as userB.
Attached:
poc.c- minimal libcurl reproducerserver.cs- minimal local Negotiate server
Impact
A request configured for one user may execute using the identity of a previously authenticated pooled connection.
Suggested fix
The credential/origin comparison should depend on whether the connection has entered Negotiate authentication state, rather than whether conn->creds is non-NULL:
if(m->want_nego_http) {
- if(conn->creds &&
+ if((conn->http_negotiate_state != GSS_AUTHNONE) &&
(!Curl_creds_same(conn->creds, m->data->state.creds) ||
!Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
return FALSE;
}