curl自定义Host头引发跨域Cookie泄露与注入风险分析

3 阅读2分钟

摘要

当指定自定义主机名时,如果本次传输也启用了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 的文档 [1] 中提到:

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

但文档中并未明确警告这同样适用于跨源重定向。

受影响版本

已在 curl 8.18.0 上测试

复现步骤

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
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

参考资料

[1] curl.se/libcurl/c/C…

影响

跨源Cookie泄露与注入风险。

官方回应

  • bagder (curl 职员):这是预期行为。欢迎为文档提供更清晰的措辞建议,但我们的确尝试记录了这一点,任何实际尝试并测试此行为的应用程序都会发现它。这不是一个安全问题。
  • 根据项目透明度的政策,此报告已请求公开披露。 biOK/hzhVF2yKaGc5mK8oeejIYuUYW8I3RsXQCFCiXX2FBas+esuFymsr4+a87Du