curl · Report #3721183
CVE-2026-8458: wrong reuse for different services
How it was found
Summary:
I found an incomplete-fix variant of CVE-2026-5545 in curl/libcurl 8.20.0.
libcurl 8.20.0 still wrongly reuses an HTTP Negotiate-authenticated connection for a later request to the same host even when the requested service principal changes via CURLOPT_SERVICE_NAME / --service-name.
In my reproduction, request 1 authenticates a persistent HTTP connection for service principal svcA. Request 2 then targets svcB on the same host, but libcurl reuses the already authenticated connection, sends no new Authorization header for svcB, and performs no new Negotiate handshake for svcB. The server still serves the svcB request under the auth context previously established for svcA.
This is wrong authenticated connection reuse across changed service principals.
This appears to be an incomplete-fix variant of CVE-2026-5545, which addressed wrong reuse of HTTP Negotiate-authenticated connections but did not cover service-principal changes in the reproduced path.
Affected version
Runtime verified on Linux x86_64 using a local build from the official curl-8.20.0 release source tarball with GSS-API / SPNEGO enabled.
Runtime output from the tested binary:
curl 8.20.0 (x86_64-pc-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.1 zlib/1.3.1 mit-krb5/1.22.1
Release-Date: 2026-04-29
Protocols: http https ipfs ipns ws wss
Features: alt-svc AsynchDNS Debug GSS-API HSTS HTTPS-proxy IPv6 Kerberos Largefile libz SPNEGO SSL threadsafe TLS-SRP UnixSockets
Steps To Reproduce:
- Build curl 8.20.0 with GSS-API / SPNEGO enabled.
cd /tmp
curl -fsSLo /tmp/curl-8.20.0.tar.xz https://curl.se/download/curl-8.20.0.tar.xz
tar -xf /tmp/curl-8.20.0.tar.xz
cd /tmp/curl-8.20.0
./configure \
--prefix=/tmp/curl-8.20.0-install \
--enable-debug \
--with-openssl \
--with-gssapi \
--disable-ldap \
--disable-ldaps \
--without-libidn2 \
--without-libpsl \
--without-brotli \
--without-zstd \
--without-libssh2 \
--without-nghttp2 \
--disable-ftp \
--disable-file \
--disable-tftp \
--disable-rtsp \
--disable-dict \
--disable-telnet \
--disable-pop3 \
--disable-imap \
--disable-smb \
--disable-smtp \
--disable-gopher \
--disable-mqtt \
--disable-manual
make -j2
- Build the attached GSS interposer and start the attached keep-alive test server.
gcc -shared -fPIC -o /tmp/libpoc_gss_service_reuse.so /tmp/poc_gss_service_reuse.c -ldl
python3 /tmp/poc_negotiate_service_server.py
- Run the vulnerable case on the built curl 8.20.0 binary.
LD_LIBRARY_PATH=/tmp/curl-8.20.0/lib/.libs \
LD_PRELOAD=/tmp/libpoc_gss_service_reuse.so \
POC_GSS_CREDS=NTLM_Alice \
/tmp/curl-8.20.0/src/curl -sv --noproxy '*' \
--negotiate --service-name svcA http://127.0.0.1:18080/svcA \
--next \
--negotiate --service-name svcB http://127.0.0.1:18080/svcB
-
Observe the vulnerable behavior:
- request 1 performs a normal Negotiate handshake for
svcA - curl logs
Reusing existing http: connection with host 127.0.0.1 - request 2 to
/svcBsends no newAuthorizationheader - request 2 succeeds as
SVC_B_REUSED_AUTH_FROM_A
- request 1 performs a normal Negotiate handshake for
Representative vulnerable output:
* Reusing existing http: connection with host 127.0.0.1
> GET /svcB HTTP/1.1
> Host: 127.0.0.1:18080
> User-Agent: curl/8.20.0
> Accept: */*
...
SVC_B_REUSED_AUTH_FROM_A
- Run the control case in a fresh process:
LD_LIBRARY_PATH=/tmp/curl-8.20.0/lib/.libs \
LD_PRELOAD=/tmp/libpoc_gss_service_reuse.so \
POC_GSS_CREDS=NTLM_Alice \
/tmp/curl-8.20.0/src/curl -sv --noproxy '*' \
--negotiate --service-name svcB http://127.0.0.1:18080/svcB
- Observe the control behavior:
- a fresh Negotiate handshake occurs for
svcB Authorization: Negotiate ...is sent forsvcB- the response is
SVC_B_FRESH_AUTH
- a fresh Negotiate handshake occurs for
Representative control output:
> GET /svcB HTTP/1.1
> Authorization: Negotiate ...
...
SVC_B_FRESH_AUTH
Supporting Material/References:
-
attached bundle:
negotiate_service_name_reuse_8200_bundle.zip -
vulnerable verbose log:
run_vulnerable_8200.txt -
control verbose log:
run_control_8200.txt -
server-side JSON event log:
server_events_8200.jsonl -
PoC sources:
poc_gss_service_reuse.cpoc_negotiate_service_server.py
Root cause notes
The requested service name participates in Negotiate auth context creation, but the HTTP Negotiate connection reuse checks in the reproduced path do not appear to treat the service name as part of the authenticated connection identity.
Relevant code paths in current source include:
lib/http_negotiate.cusingSTRING_SERVICE_NAME/STRING_PROXY_SERVICE_NAMElib/http_negotiate.cpassing the selected service into the SPNEGO auth pathlib/url.creuse matching for authenticated connections not appearing to account for changed service principals in the reproduced case
This allows a connection authenticated for service principal A to be reused for service principal B on the same host.
Impact
Summary:
This is an authentication-target confusion issue caused by wrong authenticated connection reuse.
A request intended for service principal B can be sent over a connection already authenticated for service principal A on the same host, with no new Negotiate handshake and no new Authorization header for B.
In the attached reproduction, the /svcB request succeeds under the previously established svcA authenticated connection state, while a fresh request to /svcB requires its own Negotiate exchange and yields different behavior.
This breaks the auth-target boundary enforced by service principals and can cause requests for one target service to execute under another service principal's authenticated context.
This is materially similar to the already accepted class of curl HTTP Negotiate connection reuse vulnerabilities and is best understood as an incomplete-fix / variant of CVE-2026-5545.