HexBrain

curl · Report #3697719

CVE-2026-7168: cross-proxy Digest auth state leak

Validstatus
mediumseverity
37upvotes
29 Apr 2026disclosed

How it was found

Summary:

On libcurl 8.19.0, Proxy Digest state learned from proxyA survives an independent transfer boundary on a reused easy handle and is emitted preemptively to proxyB when the proxy is changed. In the attached C PoC, the first CONNECT to proxyB carries Proxy-Authorization: Digest ... built from proxyArealm / proxyAnonce instead of starting unauthenticated and waiting for a challenge from proxyB. The leaked header is replayable to proxyA for the same CONNECT authority and is accepted with 200, while replay to a different authority is rejected with 407. A fresh easy handle and curl_easy_reset() both suppress the leak. I also confirmed that changing proxy credentials before the second transfer still causes libcurl to build the first proxyB CONNECT from stale proxyA Digest challenge state, and that a proxyB which only advertises Basic still receives stale Digest on the first CONNECT.

Affected version

Reproduced on Linux x86_64 with: curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 OpenSSL/3.5.5 zlib/1.3.1 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 libssh2/1.11.1 nghttp2/1.68.1 ngtcp2/1.21.0 nghttp3/1.15.0 librtmp/2.3 mit-krb5/1.22.1 OpenLDAP/2.6.10

The attached C PoC also prints the runtime version string: curl_version=libcurl/8.19.0 ...

Steps To Reproduce:

  1. Extract the attached curl_proxy_connect_bundle.zip.
  2. Start the local proxy harness: python3 proxy_digest_connect_servers.py proxy_events.jsonl
  3. Compile the attached C PoC against the libcurl 8.19.0 build you want to test. On my system I used: cc -I/home/nobcoder/curl/curl/include poc_proxy_connect_digest_reuse.c /usr/lib/x86_64-linux-gnu/libcurl.so.4 -o poc_proxy_connect_digest_reuse_c
  4. Run the PoC: ./poc_proxy_connect_digest_reuse_c
  5. Observe the base case output:
    • base1_res=0 base1_code=200
    • base2_res=56 base2_code=0
    • leaked_proxy_b=Digest username="proxyuser", realm="proxyArealm", nonce="proxyAnonce", uri="victim.test:18099", ...
    • raw_first_connect_proxy_b=CONNECT victim.test:18099 HTTP/1.1 ... Proxy-Authorization: Digest username="proxyuser", realm="proxyArealm", nonce="proxyAnonce", ...
    • replay_status=200
    • replay_used_password=false
    • mismatch_status=407
  6. Observe the controls:
    • fresh easy handle: fresh_first_b=(null)
    • curl_easy_reset(): reset_first_b=(null)
    • changed credentials before proxyB: altcreds_first_b=Digest username="otheruser", realm="proxyArealm", nonce="proxyAnonce", ...
    • proxyB that only advertises Basic: basicchallenge_first_b=Digest username="proxyuser", realm="proxyArealm", nonce="proxyAnonce", ...
  7. Inspect the attached proxy_events.jsonl for the full raw request log from proxyA and proxyB, including the exact first CONNECT to proxyB.

Relevant source lines against release tag curl-8_19_0:

  • lib/urldata.h:963-966 stores proxydigest and authproxy on easy-handle state
  • lib/transfer.c:500-501 and 550-551 preserve proxy auth state across new transfers
  • lib/http_digest.c:93-97, 120, and 161-162 read stale data->state.proxydigest and build Proxy-Authorization: Digest ...
  • lib/http.c:809 and lib/http_proxy.c:225-238 place proxy auth on the CONNECT path
  • lib/url.c:588-595 and 904-919 show curl already separates connections by proxy identity, so this is not just wrong cross-proxy connection reuse

Impact

Summary:

This is a cross-proxy authentication boundary break. A later attacker-controlled proxy can receive a Proxy-Authorization: Digest response that belongs to a different proxy’s CONNECT challenge, and in the attached C PoC that leaked header is replayable to proxyA for the same CONNECT authority and is accepted with 200 without the attacker knowing the password. A different CONNECT authority is rejected with 407, which shows the leaked header retains real authentication value rather than being stale metadata only. Fresh easy handles and curl_easy_reset() do not leak the header, which ties the issue to stale easy-handle-scoped Proxy Digest state crossing proxy identity boundaries.

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