libcurl自定义Host头引发跨源Cookie泄露与注入风险

6 阅读2分钟

libcurl自定义Host头引发跨源Cookie泄露与注入风险

概述

当指定自定义主机名,并且本次传输也启用了Cookie引擎时,该主机名将用于Cookie匹配。即使在跨源重定向中,这个匹配行为依然持续存在,尽管最初提供的自定义主机名已被移除。

技术细节

cookiehost 是从自定义的 Host 头中设置的:

lib/http.c http_set_aptr_host

ptr = Curl_checkheaders(data, STRCONST("Host"));
if(ptr && (!data->state.this_is_a_follow || curl_strequal(data->state.first_host, conn->host.name))) {
    /* 如果我们有给定的自定义 Host: 头,我们会提取主机名,以便以后可能用于Cookie处理。
       我们只允许在非重定向情况下使用自定义 Host: 头,因为在重定向请求中设置 Host: 是非常危险的。
       除非主机名与第一个主机名相同! */
    char *cookiehost;
    CURLcode result = copy_custom_value(ptr, &cookiehost);
    // ...
    aptr->cookiehost = cookiehost;
}

cookiehost 既用于发送Cookie,也用于处理 Set-Cookie

lib/http.c http_header_s

v = (data->cookies && data->state.cookie_engine) ? HD_VAL(hd, hdlen, "Set-Cookie:") : NULL;
// ...
/* 如果有自定义设置的 Host: 名称,则在此处使用,否则使用真实的远端主机名。 */
const char *host = data->state.aptr.cookiehost ?
  data->state.aptr.cookiehost : conn->host.name;
// ...
result = Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host,
                         data->state.up.path, secure_context);

lib/http.c http_cookies

const char *host = data->state.aptr.cookiehost ?
  data->state.aptr.cookiehost : data->conn->host.name;
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
result = Curl_cookie_getlist(data, data->conn, &okay, host, &list);

由于在重定向过程中 cookiehost 没有被清除,这种行为会在跨源重定向中持续存在。

libcurl 关于 CURLOPT_HTTPHEADER 的文档中提到:

如果本次传输也启用了Cookie引擎,指定的主机名将用于Cookie匹配...

但该文档没有明确警告这也适用于跨源重定向。

受影响版本

已在 curl 8.18.0 上测试。

复现步骤

以下示例展示了如何复现该问题。假设有两个本地服务 a.com:8000b.com:8001,它们会进行两次重定向。

复现代码及输出

user@pc:~$ curl -v -L -c cookies.txt -H "Host: example.com" --resolve b.com:8001:127.0.0.1 --resolve a.com:8000:127.0.0.1 a.com:8000
* Added b.com:8001:127.0.0.1 to DNS cache
* Added a.com:8000:127.0.0.1 to DNS cache
* Hostname a.com was found in DNS cache
*   Trying 127.0.0.1:8000...
* Established connection to a.com (127.0.0.1 port 8000) from 127.0.0.1 port 56874 
* using HTTP/1.x
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.18.0
> Accept: */*
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 302 Found
< Server: BaseHTTP/0.6 Python/3.12.3
< Date: Mon, 19 Jan 2026 12:35:05 GMT
< Location: http://b.com:8001/
* Added cookie ccc="secret" for domain example.com, path /, expire 0
< Set-Cookie: ccc=secret; Path=/
< Content-Length: 0
< 
* shutting down connection #0
* Clear auth, redirects to port from 8000 to 8001

* Hostname b.com was found in DNS cache
*   Trying 127.0.0.1:8001...
* Established connection to b.com (127.0.0.1 port 8001) from 127.0.0.1 port 43966 
* using HTTP/1.x
> GET / HTTP/1.0
> Host: b.com:8001
> User-Agent: curl/8.18.0
> Accept: */*
> Cookie: ccc=secret
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 302 Found
< Server: BaseHTTP/0.6 Python/3.12.3
< Date: Mon, 19 Jan 2026 12:35:05 GMT
* Added cookie bbb="test" for domain example.com, path /, expire 0
< Set-Cookie: bbb=test; Path=/
< Location: http://a.com:8000/check
< Content-Length: 0
< 
* shutting down connection #1
* Clear auth, redirects to port from 8001 to 8000

* Hostname a.com was found in DNS cache
*   Trying 127.0.0.1:8000...
* Established connection to a.com (127.0.0.1 port 8000) from 127.0.0.1 port 56882 
* using HTTP/1.x
> GET /check HTTP/1.0
> Host: example.com
> User-Agent: curl/8.18.0
> Accept: */*
> Cookie: bbb=test; ccc=secret
> 
* Request completely sent off
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: BaseHTTP/0.6 Python/3.12.3
< Date: Mon, 19 Jan 2026 12:35:05 GMT
< Content-Length: 0
< 
* shutting down connection #2

最终生成的cookies.txt文件

user@pc:~$ cat cookies.txt 
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

example.com	FALSE	/	FALSE	0	bbb	test
example.com	FALSE	/	FALSE	0	ccc	secret

参考资料

影响

跨源Cookie泄露与注入风险。FINISHED biOK/hzhVF2yKaGc5mK8oeejIYuUYW8I3RsXQCFCiXX2FBas+esuFymsr4+a87Du