HexBrain

curl · Report #4114965

Explicit NO_PROXY /0 CIDR rules fail to bypass HTTP proxy for IP-literal requests

Informativestatus
lowseverity
13upvotes
17 Oct 2026disclosed

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:

  1. Build curl from tag curl-8_23_0, or use a curl 8.23.0 executable. Extract the attached curl-no-proxy-zero.zip with unzip curl-no-proxy-zero.zip and enter package-no-proxy.

  2. From the extracted package-no-proxy directory, 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
    
  3. The script starts only local loopback origin and proxy listeners. It compares NO_PROXY=0.0.0.0/0 with NO_PROXY=0.0.0.0/1, NO_PROXY=::/0 with NO_PROXY=::/1 when IPv6 loopback is available, and the mixed 0.0.0.0/0,::/0 rule with a named-host control. It uses only the synthetic credential demo:secret and makes no external network request.

  4. Expected: the /0 rules send IP-literal requests directly to their origins, while the mixed rule still proxies the named host. Observed: both /0 IP-literal requests reach the proxy with an absolute-form request target and origin Basic credentials; the /1 controls 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}

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