curl · Report #3973234
53: HTTP/1 CONNECT chunked-407 trailers bypass CURLOPT_SUPPRESS_CONNECT_HEADERS and lose CURLH_CONNECT classification
How it was found
Summary
When an HTTP/1 proxy returns a chunked 407 Proxy Authentication Required during a CONNECT handshake, any trailer fields in that response are delivered to the header callback even when CURLOPT_SUPPRESS_CONNECT_HEADERS is enabled, and are classified as CURLH_TRAILER instead of CURLH_CONNECT in the Headers API. This allows a proxy to inject metadata that an application receives and queries as if it came from the certificate-verified HTTPS origin.
Tested at commit c2676bf9e639e31e44b12a2cc82cf2046231b056.
Root cause
single_header() in lib/cf-h1-proxy.c:486 sends ordinary CONNECT response lines with CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT. When the 407 body is chunked, the CONNECT state machine enters KEEPON_IGNORE and feeds bytes to the shared Curl_httpchunk_read() decoder (lib/cf-h1-proxy.c:611). That decoder emits trailer fields with only CLIENTWRITE_HEADER | CLIENTWRITE_TRAILER (lib/http_chunks.c:273-279) — it has no context that it is consuming a CONNECT response.
The missing CLIENTWRITE_CONNECT bit causes two downstream failures:
- Suppression bypass —
lib/sendf.c:190suppresses non-body CONNECT output only whenCLIENTWRITE_CONNECTis present, so the trailer reaches the header callback. - Misclassification —
lib/headers.c:331assignsCURLH_CONNECTonly whenCLIENTWRITE_CONNECTis set; without it, the field becomesCURLH_TRAILER, sharing the same origin and request scope as genuine origin trailers.
Impact
A proxy-controlled trailer can share a field name with an origin trailer and appear as two CURLH_TRAILER entries in the same request scope — one from the proxy, one from the verified origin — despite suppression being enabled. Querying the proxy field as CURLH_CONNECT returns CURLHE_MISSING.
I have not identified a curl-internal or deployed application that makes a security-sensitive decision based on trailer classification, so I am not claiming a demonstrated exploit or specific severity. The concrete issue is that proxy-controlled metadata crosses the CONNECT/origin classification boundary in a way that contradicts the documented behavior of both CURLOPT_SUPPRESS_CONNECT_HEADERS and CURLH_CONNECT.
Reproduction
- Build an affected libcurl release with HTTP/1 proxy tunneling, chunked transfer decoding, TLS, and the Headers API enabled.
- Start an HTTPS origin with a certificate that the client trusts.
- Configure the HTTPS origin to return a chunked response containing a trailer with a recognizable field name and value.
- Start an HTTP/1 proxy that accepts a persistent client connection and records successive
CONNECTrequests. - Have the proxy answer the first unauthenticated
CONNECTrequest with407 Proxy Authentication Required,Transfer-Encoding: chunked, and a zero-length chunk followed by a trailer field. - Give the proxy trailer the same field name as the HTTPS origin trailer but a distinguishable value.
- Keep the proxy connection open, accept a second authenticated
CONNECTrequest, return a successful tunnel response, and relay the connection to the HTTPS origin. - Configure a libcurl easy handle for the HTTPS origin through the proxy and provide valid proxy credentials.
- Force HTTP/1 for the proxy exchange, enable
CURLOPT_SUPPRESS_CONNECT_HEADERS, register a header callback, and configure normal certificate verification for the origin. - Perform the request and confirm that the authenticated proxy tunnel and certificate-verified HTTPS transfer both complete successfully.
- Inspect values delivered to the header callback.
- Observe the suppression bypass: the callback receives the trailer from the proxy's chunked 407 response despite
CURLOPT_SUPPRESS_CONNECT_HEADERS. - Query the shared field name through
curl_easy_header()using theCURLH_TRAILERorigin. - Observe the provenance error: both the proxy-controlled value and the genuine HTTPS-origin value appear as trailer entries in the same request scope.
- Query the proxy-controlled field using
CURLH_CONNECTand confirm that it is missing instead of being associated with the CONNECT exchange. - As a decoding control, disable HTTP transfer decoding and repeat the transfer. Confirm that the chunk decoder no longer emits either trailer while the tunnel and HTTPS transfer otherwise complete.
- Confirm a fix by carrying CONNECT provenance into trailer delivery whenever the chunk decoder is processing a CONNECT response body.
- Repeat the original transfer and verify that the proxy trailer is suppressed from callbacks when suppression is enabled, is classified as
CURLH_CONNECTinternally, and never appears as an originCURLH_TRAILER. - Confirm that the genuine trailer from the tunneled HTTPS origin remains available as
CURLH_TRAILER.
Expected result
All headers and trailers belonging to a proxy CONNECT exchange must retain CONNECT provenance. Suppression must cover them, and they must never be classified in the same origin scope as HTTPS response trailers.
Vulnerable result
The shared chunk decoder emits CONNECT-response trailers without the CONNECT marker. Proxy-controlled metadata bypasses suppression and is stored as an ordinary trailer indistinguishable by provenance from metadata sent by the verified HTTPS origin.
Suggested fix
Propagate CONNECT provenance into the chunk decoder so that trailers parsed while consuming a CONNECT response body retain CLIENTWRITE_CONNECT. A caller-supplied write-type flag or equivalent context is preferable to globally marking chunk trailers, since ordinary origin trailers must remain CURLH_TRAILER.
Impact
Summary:
A malicious configured proxy can inject trailer metadata that an application receives and queries as though it came from the verified HTTPS origin. If the application trusts trailer provenance for authorization, integrity checks, routing, or other security decisions, the proxy may spoof those values or influence policy despite TLS verification; no universal exploit in curl itself was demonstrated.