curl · Report #4053116
curl CONNECT response header colon-check bypass in cf-h1-proxy single_header — proxy authentication bypass via injected HTTP/1.x status line
How it was found
Summary
In the HTTP/1.1 proxy-CONNECT response parser (lib/cf-h1-proxy.c, single_header()), the per-line counter data->req.headerline (a.k.a. k->headerline) is never incremented — only the tunnel-local ts->headerlines is. Curl_req_soft_reset() resets k->headerline to 0 before the CONNECT exchange begins, and it stays 0 for the entire CONNECT response. Curl_verify_header() (lib/http.c) treats k->headerline < 2 as "this is a status line" and returns CURLE_OK without performing its colon-presence check, so that colon check — which exists precisely to reject garbage/injected lines in the header area — is dead code on the CONNECT path. The normal HTTP response path (http_rw_hd) increments k->headerline (if(!k->headerline++)), so the guard works there and only there.
Because the colon check is inert on the CONNECT path, a malicious proxy can inject a line shaped like an HTTP/1.x status line into the header area of a 407 Proxy Authentication Required response. on_resp_header() matches any line beginning with HTTP/1. (without checking whether it is the first/status line, and without requiring a colon) and overwrites k->httpcode and data->info.httpproxycode with the parsed status code. Injecting HTTP/1.1 200 OK: x overwrites the 407 with 200. The end-of-headers empty-line handler then sees httpcode == 200 (not 407) and proceeds to KEEPON_DONE; recv_CONNECT_resp() sees httpproxycode/100 == 2 and skips Curl_http_auth_act(); H1_CONNECT() sees the same 2xx and marks the tunnel H1_TUNNEL_ESTABLISHED. The client believes the tunnel is up, never retries with proxy authentication, and sends the real request — Cookie, Authorization, POST bodies, and the Proxy-Authorization header from the CONNECT request — over the attacker's connection.
The injected line must carry a colon (e.g. HTTP/1.1 200 OK: x) to satisfy the secondary colon check in Curl_headers_push() → namevalue(). This does not change the root cause; it only shapes the payload.
| Impact variant (all verified end-to-end) | Target scheme | What the attacker captures / controls |
|---|---|---|
| Plaintext request leak | http:// target | Full request: method, Host, Cookie, Authorization, POST body; attacker can also return a forged response (integrity) |
| TLS ClientHello intercept | https:// target | TLS ClientHello incl. SNI and connection metadata; TLS content stays encrypted; attacker may send a TLS alert to kill the connection (availability) |
| Proxy-credential capture | any CONNECT | Proxy-Authorization: Basic from the CONNECT request, base64-decoded to user:password |
| No-auth-configured hijack | any target, no --proxy-user | Tunnel still marked established; plaintext HTTP request captured |
Affected versions
- curl 8.21.0-DEV (mbedTLS 3.6.5 debug build).
- Current master
9b2caf3(8.22.0-DEV, verified 2026-08-31). Still affected. all five verification checks pass; verbose output is byte-for-byte identical to 8.21.0-DEV: the407is overwritten to200by the injected line,* CONNECT tunnel established, response 200is printed, and the tunneled request carryingCookie: secret=session123+Authorization: Bearer sensitive_tokenis captured (curl/8.22.0-DEVUser-Agent). lib/cf-h1-proxy.csingle_header()(master line 474) still only doests->headerlines++;data->req.headerlineis never incremented, andkis aconstpointer (line 470) so the existing code cannot mutate it. No fix has landed.
PoC
The attacker operates a malicious HTTP proxy (or is a man-in-the-middle between the curl client and the legitimate proxy — curl's documented attacker capability "B"). The client uses curl with --proxy-tunnel (-p) or simply requests an https:// target through the proxy (which forces a CONNECT), and has proxy authentication configured (--proxy-user / CURLOPT_PROXYUSERPWD). The client's request also carries sensitive headers (Cookie, Authorization).
- The client sends
CONNECT <host>:<port> HTTP/1.1to the attacker's proxy, including aProxy-Authorization: Basic <base64(user:pass)>header. - The attacker responds with a
407challenge and, in the header area, an injected line:
The injectedHTTP/1.1 407 Proxy Authentication Required\r\n Proxy-Authenticate: Basic realm="proxy"\r\n HTTP/1.1 200 OK: x\r\n Content-Length: 0\r\n \r\nHTTP/1.1 200 OK: xcarries a colon (required to pass the secondarynamevalue()colon check) while still matching theHTTP/1.prefix pattern inon_resp_header(). Curl_verify_header()returnsCURLE_OKfor the injected line (headerline == 0 < 2, colon check skipped).Curl_headers_push()→namevalue()accepts the line (it contains a colon).on_resp_header()matches theHTTP/1.xpattern and overwritesk->httpcode/data->info.httpproxycodefrom407to200.- At the empty line,
httpcodeis200, so the handler takes theKEEPON_DONEbranch instead ofKEEPON_IGNORE. recv_CONNECT_resp()seeshttpproxycode/100 == 2and does not callCurl_http_auth_act()— no authentication retry,data->req.newurlis not set.H1_CONNECT()sees2xxand marks the tunnelH1_TUNNEL_ESTABLISHED;cf_h1_proxy_connectsetscf->connected = TRUE.- The client, believing the tunnel is established, sends the real request over the attacker's connection. For an
http://target this is the full plaintext request (Cookie,Authorization, POST body); for anhttps://target it is the TLS ClientHello (SNI exposed, TLS content encrypted).
The negative control — the same injection without -p (normal HTTP proxy path through http_rw_hd, where k->headerline is incremented) — does not overwrite httpcode, does not establish a tunnel, and does not bypass authentication, confirming the bug is specific to the CONNECT response path. The full proof-of-concept script (malicious proxy) is available on request.
Impact
- CVSS 3.1: 9.6 (Critical) —
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N. - Trigger conditions: The attacker must operate a malicious HTTP proxy or be positioned as a MITM between the client and the legitimate proxy (curl's documented attacker capability "B"). No privileges on the client host are required. The client must use CONNECT tunnelling (
-p/--proxy-tunnel, or anyhttps://request through a proxy). The injected line must carry a colon to pass the secondarynamevalue()check. - Realized impact:
http://targets: full plaintext request captured —Cookie,Authorization, POST bodies, and all custom headers; the attacker can also return a forged HTTP response (integrity). Session hijacking and API-key/credential disclosure.- Proxy authentication is fully bypassed — even with
CURLOPT_PROXYUSERPWDset, the auth retry never fires. - The
Proxy-Authorization: Basicheader from the CONNECT request is captured and base64-decoded to plaintext proxy credentials. https://targets: TLS ClientHello (incl. SNI) is intercepted; TLS-encrypted content is not exposed. The attacker may send a TLS alert to terminate the connection (availability), or complete a MITM if the client has disabled TLS verification (-k).
- Severity bound: The C:H/I:H is fully realized for plaintext HTTP targets and for proxy-credential capture. For HTTPS targets the confidentiality impact is bounded to SNI/connection-metadata exposure (TLS content remains encrypted), and the availability impact is limited to connection termination. The attack requires the attacker to control the proxy or be a MITM; it does not grant the attacker any new network position they did not already hold, but it defeats proxy authentication — the mechanism intended to gate access to the proxy and to the upstream — and turns a proxy that would have rejected the unauthenticated CONNECT into one that silently exfiltrates the client's request and credentials.