Skip to content

Extra "status line" in proxy CONNECT response overrides the real response code (407 -> 200), bypassing proxy auth #23068

Description

@bupt-Yy-young

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions