curl · Report #3623064
HTTP/1.1 Response Desynchronization via conflicting CL/TE headers in Proxy CONNECT
How it was found
Summary:
curl fails to prioritize the Transfer-Encoding: chunked header over Content-Length in HTTP/1.1 proxy responses (specifically 407/401 auth challenges), violating RFC 9112 Section 6.1.
I have identified the root cause in cf-h1-proxy.c. In the response-handling loop (around line 466), the code checks for a Content-Length based body (if(ts->cl)) before checking for chunked_encoding. Because it finds a CL value, it decrements the counter and sets the state to KEEPON_DONE once the short CL is reached, skipping the chunked-encoding parser entirely.
This leaves the remainder of the chunked message in the socket buffer. On the subsequent CONNECT retry (e.g., after providing Digest credentials), curl reads the leftover smuggled data as the start of the new response, leading to a protocol desynchronization and a connection failure.
Affected version
I use a local build from the commit 80b6cd92af1891d9645923db3bbd453c99e99d45
./build/src/curl -V
curl 8.19.1-DEV (Darwin) libcurl/8.19.1-DEV OpenSSL/3.6.1 zlib/1.2.12 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.68.0 OpenLDAP/2.4.28/Apple
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtsp smb smbs smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz NTLM PSL SSL threadsafe TLS-SRP UnixSockets zstd
Steps To Reproduce:
- I've include a POC proxy Python script,
proxy_cl_te_desync.py. - Run the proxy
python3 proxy_cl_te_desync.py - Execute curl using the proxy
curl -v -x http://127.0.0.1:9999 --proxy-user user:pass --proxy-digest https://example.com - Observe that curl reads only the bytes specified by CL, leaving the chunked data to "poison" the next read, resulting in Invalid response header
./build/src/curl -v -x http://127.0.0.1:9999 --proxy-user user:pass --proxy-digest https://example.com
* Trying 127.0.0.1:9999...
* CONNECT: no ALPN negotiated
* allocate connect buffer
* Proxy auth using Digest with user 'user'
* Establish HTTP proxy tunnel to example.com:443
> CONNECT example.com:443 HTTP/1.1
> Host: example.com:443
> User-Agent: curl/8.19.1-DEV
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Digest realm="test", nonce="abc123", qop="auth"
< Content-Length: 5
< Transfer-Encoding: chunked
* CONNECT responded chunked
<
* Ignore 5 bytes of response-body
* Proxy auth using Digest with user 'user'
* Establish HTTP proxy tunnel to example.com:443
> CONNECT example.com:443 HTTP/1.1
> Host: example.com:443
> Proxy-Authorization: Digest username="user", realm="test", nonce="abc123", uri="example.com:443", cnonce="Q9bDz1eQ+MAmeNi2", nc=00000001, qop=auth, response="df6fbefe412a23d6edddc1f42109537f"
> User-Agent: curl/8.19.1-DEV
> Proxy-Connection: Keep-Alive
>
< THIS-SHOULD-BE-IGNORED
< 0
* Invalid response header
* closing connection #0
curl: (43) Invalid response header
Additional Notes (Disclosure):
I have used an AI assistant strictly to help format this report and ensure grammatical clarity. All technical research, including the identification of the specific flaw in cf-h1-proxy.c, the log analysis, and the creation of the functional Python PoC, were performed and verified manually by me.
Impact
Summary:
An attacker can smuggle a custom message into the connection buffer, although this is unlikely to be a primary attack vector since a malicious proxy already possesses the privileges required to modify or drop traffic. The real significance of this finding is RFC 9112 correctness and the integrity of curl’s internal state machine. By prioritizing Content-Length over Transfer-Encoding, curl creates a protocol desynchronization where the client and proxy lose track of the byte stream. This breaks the expected isolation between separate HTTP transactions on the same connection, potentially allowing injected data to be processed as the response to a subsequent, sensitive request (like a credentialed retry).