Environment: curl 8.22.0 on Linux (also reproduced on current master). libcurl + CLI, any https://
URL through an HTTP proxy (also affects -p and the CONNECT-UDP/MASQUE path).
What happens
When curling through an HTTP proxy, if the CONNECT response contains a second status-looking line,
that line overwrites the status curl parsed from the real first line. A genuine 407 Proxy Authentication Required turns into "200", the tunnel is considered established, and curl sends the
inner request without Proxy-Authorization.
Repro
Fake proxy answers CONNECT host:443 with:
HTTP/1.1 407 Proxy Authentication Required
HTTP/1.1 200 Connection established: x
Proxy-Authenticate: Basic realm="proxy"
Content-Length: 0
then:
$ curl -v -x http://127.0.0.1:8080/ https://example.invalid/
Observed:
* CONNECT tunnel established, response 200
> ... (inner request / TLS ClientHello sent, no Proxy-Authorization)
Control: without the extra HTTP/1.1 200 ... line curl correctly fails with
CONNECT tunnel failed, response 407 and sends nothing.
Note: 8.21.0 added validation of CONNECT response lines (Curl_verify_header), so the forged line
needs a : separator to pass as a header (as above). The overwriting branch also exists in 8.20.0
and earlier (without that validation), so those look at least as exposed.
Why
lib/cf-h1-proxy.c on_resp_header() runs for every response line, and this branch takes any line
matching HTTP/1.x NNN and overwrites k->httpcode / data->info.httpproxycode. The tunnel result
is then decided with httpproxycode / 100 == 2, so the fake line wins over the real 407.
Expected vs actual
Expected: the real (first) status line decides the CONNECT outcome. Actual: a later status-looking
line can flip 407 to 200, so the client thinks the proxy accepted the tunnel without auth and ships
the inner request (lenient/broken proxies and on-path injectors can let that through).
Fix direction: take the final status only from the first status line (handle real 1xx per RFC 7231
6.2 separately), and never let a header-like line update the code used for the tunnel decision —
especially not after a final 4xx/5xx was parsed.
Environment: curl 8.22.0 on Linux (also reproduced on current master). libcurl + CLI, any
https://URL through an HTTP proxy (also affects
-pand the CONNECT-UDP/MASQUE path).What happens
When curling through an HTTP proxy, if the CONNECT response contains a second status-looking line,
that line overwrites the status curl parsed from the real first line. A genuine
407 Proxy Authentication Requiredturns into "200", the tunnel is considered established, and curl sends theinner request without
Proxy-Authorization.Repro
Fake proxy answers
CONNECT host:443with:then:
Observed:
Control: without the extra
HTTP/1.1 200 ...line curl correctly fails withCONNECT tunnel failed, response 407and sends nothing.Note: 8.21.0 added validation of CONNECT response lines (
Curl_verify_header), so the forged lineneeds a
:separator to pass as a header (as above). The overwriting branch also exists in 8.20.0and earlier (without that validation), so those look at least as exposed.
Why
lib/cf-h1-proxy.con_resp_header()runs for every response line, and this branch takes any linematching
HTTP/1.x NNNand overwritesk->httpcode/data->info.httpproxycode. The tunnel resultis then decided with
httpproxycode / 100 == 2, so the fake line wins over the real 407.Expected vs actual
Expected: the real (first) status line decides the CONNECT outcome. Actual: a later status-looking
line can flip 407 to 200, so the client thinks the proxy accepted the tunnel without auth and ships
the inner request (lenient/broken proxies and on-path injectors can let that through).
Fix direction: take the final status only from the first status line (handle real 1xx per RFC 7231
6.2 separately), and never let a header-like line update the code used for the tunnel decision —
especially not after a final 4xx/5xx was parsed.