curl · Report #3673277
libcurl stale CURLOPT_AUTOREFERER leaks a previous request URL to a different origin on a reused easy handle
How it was found
Summary:
libcurl keeps a stale data->state.referer after an HTTP redirect when CURLOPT_AUTOREFERER is enabled. Curl_http_follow() stores the previous URL into data->state.referer at lib/http.c:1166-1189, and later requests reuse that value when building Referer: at lib/http.c:2954-2957. In my local checkout, lib/transfer.c:495-503 resets follow/auth state but does not reset data->state.referer. As a result, after one redirecting transfer completes, a subsequent unrelated request on the same easy handle can send a stale Referer: header to a different origin. I reproduced this on curl 8.19.0 / libcurl 8.19.0 on x86_64 Linux, and included controls showing the leak disappears when the second request uses a new easy handle or when CURLOPT_AUTOREFERER is disabled. An attachment with the PoC source, server script, and captured output is included.
Affected version
Reproduced on:
curl 8.19.0 (x86_64-pc-linux-gnu) libcurl/8.19.0 OpenSSL/3.5.5 zlib/1.3.1 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 libssh2/1.11.1 nghttp2/1.68.1 ngtcp2/1.21.0 nghttp3/1.15.0 librtmp/2.3 mit-krb5/1.22.1 OpenLDAP/2.6.10
Platform:
Linux x86_64
Steps To Reproduce:
- Start the attached local server:
python3 -u stale_autoreferer_server.py 18084 - Build the attached PoC:
cc poc_stale_autoreferer.c -o poc_stale_autoreferer $(curl-config --cflags --libs) - Run the PoC:
./poc_stale_autoreferer - Observe the base case output:
req1=0 effective=http://b.test:18084/final referer=http://a.test:18084/start?token=SECRET123req2=0 effective=http://attacker.test:18084/check referer=http://a.test:18084/start?token=SECRET123 - Observe control 1:
with a new easy handle for request 2,
attacker.testreceivesreferer=(null) - Observe control 2:
with
CURLOPT_AUTOREFERERdisabled, the staleReferer:is not sent on request 2 - Observe the attached server log for the base case:
{"host": "a.test:18084", "path": "/start?token=SECRET123", "referer": null}{"host": "b.test:18084", "path": "/final", "referer": "http://a.test:18084/start?token=SECRET123"}{"host": "attacker.test:18084", "path": "/check", "referer": "http://a.test:18084/start?token=SECRET123"}
Impact
Summary:
This causes cross-origin disclosure of the previous transfer URL through a stale Referer: header on a later request using the same easy handle. Because the leaked URL can include query parameters, path segments, signed URLs, internal endpoint names, or other sensitive values, an attacker-controlled origin can receive data unrelated to the current request. The included controls show that the leak is specifically caused by persisted CURLOPT_AUTOREFERER state across transfers, not normal referer behavior.