HexBrain

curl · Report #3721183

CVE-2026-8458: wrong reuse for different services

Validstatus
lowseverity
2upvotes
24 Jun 2026disclosed

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:

  1. 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
  1. 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
  1. 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
  1. 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 /svcB sends no new Authorization header
    • request 2 succeeds as SVC_B_REUSED_AUTH_FROM_A

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
  1. 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
  1. Observe the control behavior:
    • a fresh Negotiate handshake occurs for svcB
    • Authorization: Negotiate ... is sent for svcB
    • the response is SVC_B_FRESH_AUTH

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.c
    • poc_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.c using STRING_SERVICE_NAME / STRING_PROXY_SERVICE_NAME
  • lib/http_negotiate.c passing the selected service into the SPNEGO auth path
  • lib/url.c reuse 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.

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