curl · Report #4114965
Explicit NO_PROXY /0 CIDR rules fail to bypass HTTP proxy for IP-literal requests
How it was found
Summary:
With an HTTP proxy configured, I found that curl sends IPv4 address-literal requests through it with NO_PROXY=0.0.0.0/0, and IPv6 address-literal requests through it with NO_PROXY=::/0. A zero-bit CIDR prefix should match every address in its family, so those requests should bypass the proxy. A mixed rule such as NO_PROXY=0.0.0.0/0,::/0 lets a caller keep numeric destinations direct while continuing to proxy named hosts. In my local proof of concept, the proxy received an absolute-form HTTP request target including the /private path and a synthetic origin Authorization header, while /1 controls connected directly to the origins. I used AI assistance during the investigation and drafting, then reviewed the attached PoC and evidence myself.
Affected version
Reproduced with curl source tag curl-8_23_0 (commit 3163ec065643ba20709f190111f42c3e1fe95273) on macOS, Darwin 25.5.0, arm64. The local CMake build reports 8.23.0-DEV in its version banner; the checkout is the exact curl-8_23_0 tag.
$ /path/to/built/curl -V
curl 8.23.0-DEV (Darwin) libcurl/8.23.0-DEV OpenSSL/4.0.3 nghttp2/1.70.0
Steps To Reproduce:
-
Build curl from tag
curl-8_23_0, or use a curl 8.23.0 executable. Extract the attachedcurl-no-proxy-zero.zipwithunzip curl-no-proxy-zero.zipand enterpackage-no-proxy. -
From the extracted
package-no-proxydirectory, run the Python 3 standard-library PoC with the path to that curl executable:python3 poc_no_proxy_zero.py /absolute/path/to/built/curl -
The script starts only local loopback origin and proxy listeners. It compares
NO_PROXY=0.0.0.0/0withNO_PROXY=0.0.0.0/1,NO_PROXY=::/0withNO_PROXY=::/1when IPv6 loopback is available, and the mixed0.0.0.0/0,::/0rule with a named-host control. It uses only the synthetic credentialdemo:secretand makes no external network request. -
Expected: the
/0rules send IP-literal requests directly to their origins, while the mixed rule still proxies the named host. Observed: both/0IP-literal requests reach the proxy with an absolute-form request target and origin Basic credentials; the/1controls reach their origins directly and the named host remains proxied. One local run included:NO_PROXY=0.0.0.0/0: reached proxy, including origin Basic credentials GET http://127.0.0.1:58653/private HTTP/1.1 Authorization: Basic ZGVtbzpzZWNyZXQ= NO_PROXY=0.0.0.0/1: reached origin directly (control) NO_PROXY=::/0: reached proxy, including origin Basic credentials NO_PROXY=::/1: reached IPv6 origin directly (control)
The source explains the result: lib/proxy.c:166,175-189 gives a bare address and an explicit /0 the same bits = 0 value; cidr4_match() at lines 72-86 treats zero as an exact-address comparison, and cidr6_match() at lines 101-103 changes zero to 128 bits. The ZIP includes the complete PoC, line-numbered source and documentation excerpts, and the captured local run.
Impact
Summary:
When a caller uses /0 to keep IP-literal destinations off a configured HTTP proxy while still proxying named hosts, curl can instead send the proxy an absolute-form HTTP request target including its path, plus origin Authorization credentials supplied with --user. The proxy or its logs can therefore receive paths and credentials intended only for the origin. This requires an explicit /0 rule, a configured proxy, and a sensitive plain-HTTP request; a conventional HTTPS CONNECT proxy does not see origin headers inside TLS without interception. Suggested severity: Low.
{F6991259}