HexBrain

curl · Report #3773293

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

Informativestatus
mediumseverity
2upvotes
3 Jun 2026disclosed

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.

  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

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

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

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

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

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