curl · Report #3707747
libcurl 8.20.0 incomplete fix for CVE-2026-7168: changing only CURLOPT_PROXYPORT leaks stale Proxy Digest auth to a different proxy
How it was found
Summary:
I found an incomplete-fix variant of CVE-2026-7168 in curl 8.20.0. The 8.20.0 fix clears state.proxydigest / state.authproxy when CURLOPT_PROXY changes, but not when only CURLOPT_PROXYPORT changes. On the same easy handle, request 1 through proxyA (CURLOPT_PROXYPORT=18197) learns Proxy Digest state, and request 2 through proxyB (CURLOPT_PROXYPORT=18198) sends a first CONNECT with stale Proxy-Authorization: Digest ... built from proxyArealm / proxyAnonce. The attached minimal C PoC keeps CURLOPT_PROXY fixed as http://127.0.0.1, changes only CURLOPT_PROXYPORT, and the server output shows proxyB receiving the stale proxyA-derived Digest header on its first CONNECT. A fresh easy handle does not send that header on the first CONNECT to proxyB. This reproduces for me on the clean curl-8_20_0 release tag and also on current master faa4b0692d30986c498d91ad36223cbf02796ad1.
Affected version
Reproduced on Linux x86_64 from a local build of:
- tag:
curl-8_20_0 - commit:
a05f34973e6c4bb629d018f7cb51487be1c904d8 - runtime string from my local build:
libcurl/8.20.0-DEV OpenSSL/3.5.5 zlib/1.3.1
Also reproduced on:
- branch:
master - commit:
faa4b0692d30986c498d91ad36223cbf02796ad1 - runtime string:
libcurl/8.20.1-DEV OpenSSL/3.5.5 zlib/1.3.1
Steps To Reproduce:
-
Start the attached minimal proxy server:
python3 proxyport_digest_min_server.py -
Build the attached minimal client against
curl-8_20_0:cc -I/tmp/curl-8_20_0/include poc_proxyport_digest_min.c /tmp/curl-8_20_0/build-audit/lib/libcurl.so -Wl,-rpath,/tmp/curl-8_20_0/build-audit/lib -o poc_proxyport_digest_min_8200 -
Run it:
./poc_proxyport_digest_min_8200 -
Observe the client output:
req1_res=0req2_res=7
-
Observe the proxy output:
proxyArequest 1: noProxy-AuthorizationproxyArequest 2:Proxy-Authorization: Digest ... realm="proxyArealm", nonce="proxyAnonce", ...proxyBrequest 1:Proxy-Authorization: Digest ... realm="proxyArealm", nonce="proxyAnonce", ...
This reproducer keeps CURLOPT_PROXY fixed as http://127.0.0.1 and changes only CURLOPT_PROXYPORT from 18197 to 18198.
Relevant source lines in curl-8_20_0:
lib/setopt.c:1037—CURLOPT_PROXYPORTonly updatess->proxyportlib/setopt.c:1626— the 8.20.0 fix clearsstate.proxydigest/state.authproxyonly when theCURLOPT_PROXYstring changeslib/url.c:2059— the effective proxy port comes fromdata->set.proxyportlib/transfer.c:537andlib/transfer.c:592— proxy auth state survives across transferslib/http_digest.c:93andlib/http_digest.c:161— stale Proxy Digest state is reused to buildProxy-Authorization: Digest ...
Impact
This is an incomplete-fix variant of CVE-2026-7168. Changing only CURLOPT_PROXYPORT is enough to move the easy handle to a different proxy identity while preserving stale Proxy Digest state from the first proxy. As a result, a later proxy can receive a Proxy-Authorization: Digest header derived from another proxy’s challenge on the first CONNECT request. This is not expected negotiation state for proxyB; it is stale authenticated state crossing a proxy identity boundary.