✓ProveIt
Examples / Report #3923520 Saved analysis

CVE-2026-19931: Negotiate ambient user conn reuse

curl · 2026-08-0715.7s analysis · $0.014 in model calls

What the report claims

curl 8.21.0 on Windows with SSPI/Negotiate incorrectly reuses connections authenticated with ambient credentials for subsequent requests that specify different explicit credentials, causing the later request to be processed under the former identity.

Compare with curl's recorded outcome

Known vulnerability

A real vulnerability: CVE-2026-19931 Negotiate ambient user conn 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?
  • The code at lib/url.c:975-979 shows the credential comparison is guarded by `if(conn->creds &&`, meaning when conn->creds is NULL (as with ambient SSPI credentials), the check is entirely skipped.
  • The function already tracks Negotiate authentication state via `conn->http_negotiate_state != GSS_AUTHNONE` (used at lines 981, 1007), which is the correct condition for the fix.
  • This allows a connection authenticated as one identity (via ambient credentials) to be reused for a request with different explicit credentials, causing credential confusion - a clear security issue.
  • The deterministic checks confirm all cited symbols exist in the codebase at the target revision.
  • The issue does not fall under any 'not security issues' category in the project policy.
What still needs checking
  • The PoC and server code (poc.c, server.cs) were not provided for verification, but the code flaw is directly visible in the source.
How this analysis ran 5 claims · 13 citations verified

Checked against curl master on 2026-08-07 (a15cf1ea0796). 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 · 8 references checked out, 0 did not
2 · Claims
NVIDIA-Nemotron-3-Nano-30B-A3B
5 claims · 1813 tokens · $0.0002
3 · Evidence
4 code snippets
real curl source at a15cf1ea0796 · 3 docs via Tavily
4 · Judgment
Nemotron-3-Ultra-550b-a55b
9144 tokens · 13.3s · $0.0142
5 · Receipts
13 of 15 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 report. The flaw is confirmed in lib/url.c:976 where the credential comparison is guarded by `if(conn->creds &&`, skipping the check when ambient credentials leave conn->creds as NULL. The fix using `conn->http_negotiate_state != GSS_AUTHNONE` is correct since that state is already tracked in the same function (lines 981, 1007). This is a valid security issue — credential confusion allowing one user's request to execute as another identity.

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

Report claims

C1flaw supported

The credential comparison in url_match_auth_nego() is skipped when conn->creds is NULL, allowing a connection authenticated with ambient credentials to be reused for a request with different explicit credentials.

Read the reasoning

The code at lib/url.c:975-979 shows the credential/origin comparison is only performed when conn->creds is non-NULL. If ambient SSPI credentials result in conn->creds == NULL, the check is skipped, allowing connection reuse across different identities.

C2location supported

The flaw occurs in the url_match_auth_nego() function where the check for credential equality is guarded by conn->creds != NULL.

Read the reasoning

The function url_match_auth_nego exists at lib/url.c:969 and the specific flawed guard is at line 976.

C3trigger supported

A connection that was established using ambient SSPI credentials (conn->creds == NULL) is reused for a subsequent request that supplies explicit credentials for a different user.

Read the reasoning

The trigger condition is exactly when conn->creds == NULL (ambient credentials) but the connection has entered Negotiate auth state (http_negotiate_state != GSS_AUTHNONE). The code already has the state tracking needed for a proper fix.

C4impact supported

The later request is processed with the identity of the original connection, exposing data to the wrong user or allowing unauthorized access under the original identity.

Read the reasoning

When the credential check is skipped due to conn->creds == NULL, the connection is accepted for reuse (m->found = conn at line 1006), causing the later request to use the original connection's authenticated identity.

C5reproduction not verifiable

Run a local Negotiate server, request A without explicit credentials (authenticates as Administrator), then request B with explicit credentials for userB; on curl 8.21.0 request B reuses the connection and is processed as Administrator.

Read the reasoning

The reproduction files (poc.c, server.cs) are referenced but not included in the provided source code. The deterministic checks confirm the relevant symbols exist, but the actual reproduction cannot be verified from the provided code alone.

poc.c:0 server.cs:0

What the report says about other projects

not addressed The credential comparison should depend on whether the connection has entered Negotiate authentication state, rather than whether conn->creds is non-NULL.

not addressed The credential comparison should depend on whether the connection has entered Negotiate authentication state, rather than whether conn->creds is non-NULL.

not addressed The credential comparison should depend on whether the connection has entered Negotiate authentication state, rather than whether conn->creds is non-NULL.

Read the original report

Full report on HackerOne ↗

## 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: ```c 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: ```text 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: ```diff 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; } ```

Source code

lib/url.clines 969–1018 · definition of url_match_auth_nego
969static bool url_match_auth_nego(struct connectdata *conn,
C2: Function definition: static bool url_match_auth_nego(struct connectdata *conn, struct url_conn_match *m)
970 struct url_conn_match *m)
971{
972 /* If we are looking for an HTTP+Negotiate connection, check if this is
973 already authenticating with the right credentials. If not, keep looking
974 so that we can reuse Negotiate connections if possible. */
975 if(m->want_nego_http) {
C1: The credential comparison is guarded by `if(conn->creds &&` at line 976
C2: The flaw occurs in the if(m->want_nego_http) block starting here
C4: When m->want_nego_http is true and conn->creds is NULL, the function returns TRUE (line 1017) allowing reuse
976 if(conn->creds &&
C1: When conn->creds is NULL, the entire Curl_creds_same and Curl_peer_equal check is skipped
C2: The guard condition `if(conn->creds &&` is the location of the flaw
C3: The condition `if(conn->creds &&` means when conn->creds == NULL, the credential check is bypassed
977 (!Curl_creds_same(conn->creds, m->data->state.creds) ||
C1: Curl_creds_same comparison only runs when conn->creds is non-NULL
978 !Curl_peer_equal(conn->creds_origin, m->data->state.origin)))
C1: Curl_peer_equal comparison only runs when conn->creds is non-NULL
979 return FALSE;
980 }
981 else if(conn->http_negotiate_state != GSS_AUTHNONE) {
C3: The code already tracks Negotiate auth state via conn->http_negotiate_state != GSS_AUTHNONE
982 /* Connection is using Negotiate auth but we do not want Negotiate */
983 return FALSE;
984 }
985
986#ifndef CURL_DISABLE_PROXY
987 /* Same for Proxy Negotiate authentication */
988 if(m->want_proxy_nego_http) {
989 /* Both conn->http_proxy.user and conn->http_proxy.passwd can be
990 * NULL */
991 if(!conn->http_proxy.creds)
992 return FALSE;
993
994 if(!Curl_creds_same(m->needle->http_proxy.creds, conn->http_proxy.creds))
995 return FALSE;
996 }
997 else if(conn->proxy_negotiate_state != GSS_AUTHNONE) {
998 /* Proxy connection is using Negotiate auth but we do not want Negotiate */
999 return FALSE;
1000 }
1001#endif
1002 if(m->want_nego_http || m->want_proxy_nego_http) {
C4: If want_nego_http is true, m->found = conn is set at line 1006, marking the connection for reuse
1003 /* Credentials are already checked, we may use this connection. We MUST
1004 * use a connection where it has already been fully negotiated. If it has
1005 * not, we keep on looking for a better one. */
1006 m->found = conn;
1007 if((m->want_nego_http &&
C3: http_negotiate_state is used elsewhere in the same function to detect Negotiate auth state
1008 (conn->http_negotiate_state != GSS_AUTHNONE)) ||
1009 (m->want_proxy_nego_http &&
1010 (conn->proxy_negotiate_state != GSS_AUTHNONE))) {
1011 /* We must use this connection, no other */
1012 m->force_reuse = TRUE;
1013 return TRUE;
1014 }
1015 return FALSE; /* get another */
C4: Returns FALSE only to keep looking for a better connection, but the connection is still marked as found
1016 }
1017 return TRUE;
1018}
lib/creds.clines 158–167 · definition of Curl_creds_same
158bool Curl_creds_same(struct Curl_creds *c1, struct Curl_creds *c2)
159{
160 return (c1 == c2) ||
161 (c1 && c2 &&
162 !Curl_timestrcmp(c1->user, c2->user) &&
163 !Curl_timestrcmp(c1->passwd, c2->passwd) &&
164 !Curl_timestrcmp(c1->oauth_bearer, c2->oauth_bearer) &&
165 !Curl_timestrcmp(c1->sasl_authzid, c2->sasl_authzid) &&
166 !Curl_timestrcmp(c1->sasl_service, c2->sasl_service));
167}
lib/peer.clines 321–327 · definition of Curl_peer_equal
Show 7 lines (not cited by the judge)
321bool Curl_peer_equal(struct Curl_peer *p1, struct Curl_peer *p2)
322{
323 return (p1 == p2) ||
324 (p1 && p2 &&
325 (p1->scheme == p2->scheme) &&
326 Curl_peer_same_destination(p1, p2));
327}
include/curl/curl.hlines 833–833 · definition of CURLAUTH_NEGOTIATE
Show 1 lines (not cited by the judge)
833#define CURLAUTH_NEGOTIATE (((unsigned long)1) << 2)