libcurl 8.20.0 incomplete fix for CVE-2026-7168: changing only CURLOPT_PROXYPORT leaks stale Proxy Digest auth to a different proxy
Medium
Vulnerability Details
## 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:
1. Start the attached minimal proxy server:
`python3 proxyport_digest_min_server.py`
2. 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`
3. Run it:
`./poc_proxyport_digest_min_8200`
4. Observe the client output:
- `req1_res=0`
- `req2_res=7`
5. Observe the proxy output:
- `proxyA` request 1: no `Proxy-Authorization`
- `proxyA` request 2: `Proxy-Authorization: Digest ... realm="proxyArealm", nonce="proxyAnonce", ...`
- `proxyB` request 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_PROXYPORT` only updates `s->proxyport`
- `lib/setopt.c:1626` — the 8.20.0 fix clears `state.proxydigest` / `state.authproxy` only when the `CURLOPT_PROXY` string changes
- `lib/url.c:2059` — the effective proxy port comes from `data->set.proxyport`
- `lib/transfer.c:537` and `lib/transfer.c:592` — proxy auth state survives across transfers
- `lib/http_digest.c:93` and `lib/http_digest.c:161` — stale Proxy Digest state is reused to build `Proxy-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.
Actions
View on HackerOneReport Stats
- Report ID: 3707747
- State: Closed
- Substate: informative