HexBrain

curl · Report #3923520

CVE-2026-19931: Negotiate ambient user conn reuse

Validstatus
mediumseverity
12upvotes
3 Sept 2026disclosed

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

  1. Run the attached local Windows Negotiate server.
  2. Run the PoC as Windows user A (Administrator in my test).
  3. Request A uses CURLAUTH_NEGOTIATE without explicit credentials.
  4. Request B uses another easy handle on the same CURLM with explicit credentials for userB.
  5. 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 reproducer
  • server.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;
 }

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