CVE-2026-8924 深度分析:curl 超级Cookie 注入 — 一个尾随点让 PSL 防护形同虚设
引言
2026 年 6 月 24 日,curl 项目发布了安全公告 CVE-2026-8924,披露了一个在 Cookie 解析逻辑中潜伏近 十年 的漏洞。
这个漏洞的核心异常简单:攻击者在 Set-Cookie 响应头的 Domain 字段末尾加一个点(.),就能让 curl 的 Public Suffix List(PSL)检查完全失效,从而注入能够跨域传播的"超级 Cookie"。
CVSS 评分 9.1(严重),影响 curl 7.46.0 到 8.20.0 的所有版本。考虑到 curl/libcurl 是全球最广泛使用的网络传输库——嵌入在数万种应用、容器镜像、嵌入式系统和 IoT 设备中——保守估计 数十亿台设备受到影响。
一、背景知识:为什么需要 Public Suffix List
1.1 Cookie 的 Domain 机制
HTTP Cookie 是 Web 安全的基石之一,但其 Domain 匹配规则存在天然的"过于宽松"问题:
# 服务器 example.co.uk 设置了这样的 Cookie:
Set-Cookie: session=abc123; Domain=co.uk
# 如果没有保护机制,浏览器/HTTP 客户端会把这个 Cookie 发送给:
# - example.co.uk ✓ (合法)
# - other.co.uk ✗ (本不应该!)
# - anything.co.uk ✗ (本不应该!)如果允许 Domain=co.uk,那么任何 .co.uk 域名下的网站都能收到这个 Cookie。这破坏了 Cookie 的源隔离原则。
1.2 Public Suffix List 的作用
PSL 是 Mozilla 维护的一个公共后缀列表,它记录了所有"任何人都可以注册子域名"的域名后缀:
# PSL 中的条目示例
com # 任何人在 .com 下都能注册
co.uk # 任何人在 .co.uk 下都能注册
com.cn # 任何人在 .com.cn 下都能注册
github.io # 任何人在 .github.io 下都能创建 Pages
compute.amazonaws.com # AWS EC2 的公共域名PSL 的核心规则是:不允许为 PSL 条目设置 Cookie。
// curl 中 PSL 检查的简化逻辑(lib/cookie.c)
static bool is_public_suffix(const char *domain, const char *cookie_domain) {
// domain: "example.co.uk" (请求的域名)
// cookie_domain: "co.uk" (Set-Cookie 中的 Domain)
// 调用 PSL 库检查 cookie_domain 是否是公共后缀
// 如果是 → 拒绝这个 Cookie(不允许设置如此宽泛的 Domain)
return psl_is_cookie_domain_acceptable(psl, domain, cookie_domain);
}1.3 尾随点(Trailing Dot)是什么
在 DNS 规范中,完全限定域名(FQDN)以点结尾:
example.co.uk. ← DNS 中的完整域名,末尾的点代表"根"
example.co.uk ← 日常使用的相对域名
这两个域名在实际使用中被认为是等价的。但问题在于:PSL 数据库中的条目不包含尾随点。PSL 收录的是 co.uk,不是 co.uk.。
二、漏洞分析
2.1 根因
漏洞位于 lib/cookie.c 的 is_public_suffix() 函数中。核心问题是:在进行 PSL 匹配之前,没有对域名做"尾随点归一化"。
// 问题代码(curl 7.46.0 ~ 8.20.0,简化版)
// 文件:lib/cookie.c
static bool is_public_suffix(struct CookieInfo *cookie_info,
const char *domain,
const char *cookie_domain)
{
const psl_ctx_t *psl = cookie_info->psl;
if (!psl)
return false;
// BUG: 直接将 domain 和 cookie_domain 传给 PSL 库
// 没有去掉尾随点!
if (psl_is_cookie_domain_acceptable(psl, domain, cookie_domain)) {
return false; // PSL 认为可以接受 → 不是公共后缀
}
// 如果是公共后缀 → 拒绝这个 Cookie
infof(data, "cookie '%s' dropped, domain '%s' is a public suffix",
cookie_name, cookie_domain);
return true;
}2.2 攻击流程
攻击场景:攻击者控制了 evil.com 的 HTTP 服务器
步骤 1:受害者(curl 客户端)访问攻击者的服务器
┌──────────────────────────────────┐
│ curl https://evil.co.uk. │
│ (注意:URL 中域名带尾随点) │
└───────────────┬──────────────────┘
│ HTTP Request
▼
┌──────────────────────────────────┐
│ 攻击者服务器(evil.co.uk) │
└───────────────┬──────────────────┘
│ HTTP Response
▼
HTTP/1.1 200 OK
Set-Cookie: tracker=malicious; Domain=co.uk.
Set-Cookie: session=stolen; Domain=co.uk.
↑ 注意:Domain 值带尾随点 "co.uk."
步骤 2:curl 处理 Set-Cookie
┌──────────────────────────────────┐
│ lib/cookie.c: is_public_suffix() │
│ │
│ domain: "evil.co.uk." │
│ cookie_domain: "co.uk." │
│ │
│ PSL 检查: │
│ psl_is_cookie_domain_acceptable( │
│ psl, │
│ "evil.co.uk.", ← 带尾随点 │
│ "co.uk." ← 带尾随点 │
│ ) │
│ │
│ PSL 数据库中的条目: │
│ "co.uk" ← 不带尾随点 │
│ │
│ 结果:"co.uk." 不在 PSL 中 │
│ → PSL 检查被绕过! │
│ → Cookie 被接受 │
└──────────────────────────────────┘
步骤 3:超级 Cookie 扩散
┌──────────────────────────────────────────────┐
│ curl 的 Cookie jar 中现在有: │
│ │
│ Domain=co.uk. → tracker=malicious │
│ Domain=co.uk. → session=stolen │
│ │
│ 后续访问任何 *.co.uk 的请求都会携带这些 Cookie: │
│ │
│ curl https://bank.co.uk │
│ Cookie: tracker=malicious; session=stolen │
│ │
│ curl https://government.co.uk │
│ Cookie: tracker=malicious; session=stolen │
└──────────────────────────────────────────────┘2.3 为什么 PSL 条目不包含尾随点
PSL 的设计假设域名已经被规范化处理了。在正常的 HTTP 流程中:
- 浏览器/客户端对 URL 中的域名做规范化(去掉尾随点)
Set-Cookie中的Domain字段也被规范化- PSL 匹配使用的是规范化后的域名
但 curl 在处理带尾随点的 URL(https://evil.co.uk.)时,没有对 Set-Cookie 响应头中的 Domain=co.uk. 做同样的规范化处理,导致 PSL 匹配失败。
2.4 受影响版本范围
受影响版本:curl 7.46.0 到 8.20.0
├── 引入时间:2015 年 12 月 (commit e77b5b7453c1)
│ └── 该 commit 在 lib/cookie.c 中引入了 PSL 检查逻辑
├── 潜伏时间:约 10.5 年
└── 修复版本:curl 8.21.0 (2026 年 6 月 24 日)
└── commit 51beed175dbfc37da3113f6acce60c630c070ce8三、修复方案
3.1 官方修复代码
curl 8.21.0 中的修复非常干净——在 PSL 匹配前对域名做尾随点归一化:
// 修复后的代码(curl 8.21.0,来自 commit 51beed175dbf)
// 文件:lib/cookie.c
static bool is_public_suffix(struct Curl_easy *data,
struct CookieInfo *cookie_info,
const char *domain,
const char *cookie_domain)
{
const psl_ctx_t *psl = cookie_info->psl;
char *normalized_domain = NULL;
char *normalized_cookie_domain = NULL;
bool result = false;
if(!psl)
return false;
// 修复:去掉 domain 和 cookie_domain 的尾随点
normalized_domain = strip_trailing_dot_if_any(domain);
normalized_cookie_domain = strip_trailing_dot_if_any(cookie_domain);
if(!normalized_domain || !normalized_cookie_domain)
goto out;
// 使用规范化后的域名进行 PSL 匹配
if(psl_is_cookie_domain_acceptable(psl,
normalized_domain,
normalized_cookie_domain)) {
goto out; // 可以接受
}
// 公共后缀,拒绝这个 Cookie
infof(data, "cookie '%s' dropped, domain '%s' is a public suffix",
cookie_name, normalized_cookie_domain);
result = true;
out:
free(normalized_domain);
free(normalized_cookie_domain);
return result;
}
// 新增辅助函数
static char *strip_trailing_dot_if_any(const char *domain)
{
size_t len;
char *copy;
if(!domain)
return NULL;
len = strlen(domain);
// 去掉尾随点
if(len > 0 && domain[len - 1] == '.')
len--;
copy = malloc(len + 1);
if(!copy)
return NULL;
memcpy(copy, domain, len);
copy[len] = '\0';
return copy;
}3.2 修复的本质
修复的核心思路是:在 PSL 匹配之前,先将域名统一规范化(去掉尾随点),使得 co.uk. 在 PSL 数据库中等价于 co.uk。
这是一个设计原则层面的修复,而不是针对具体漏洞的打补丁:
- 旧代码:信任调用者传入的域名格式
- 新代码:在安全边界处统一做归一化(canonicalization)
四、影响面分析
4.1 受影响场景
CVE-2026-8924 的典型受影响场景:
1. CI/CD 流水线
├── curl 被广泛用于下载依赖、发送 Webhook
├── 如果流水线访问了恶意 HTTP 服务器
└── 后续访问同一 TLD 下的内部服务时可能泄露 Cookie
2. 容器镜像构建
├── Dockerfile 中 RUN curl https://...
├── 如果下载源被中间人攻击(带尾随点域名)
└── Cookie 可能随镜像一起分发
3. IoT 和嵌入式设备
├── 设备固件中使用 libcurl 进行 OTA 更新
├── 更新服务器域名被仿冒(带尾随点版本)
└── 固件被注入恶意 Cookie,影响后续操作
4. API 客户端
├── 后端服务使用 libcurl 调用第三方 API
├── 如果第三方 API URL 域名带尾随点
└── Cookie 泄漏到同 TLD 下的其他服务
5. 浏览器底层
├── 部分浏览器使用 libcurl 作为底层 HTTP 传输
└── 虽然浏览器通常有额外保护层,但可能存在绕过路径4.2 为什么 CVSS 评 9.1
| CVSS v3.1 指标 | 值 | 理由 |
|---|---|---|
| 攻击向量 (AV) | N (网络) | 远程 HTTP 服务器即可触发 |
| 攻击复杂度 (AC) | L (低) | 只需设置特定的 Set-Cookie 头 |
| 权限要求 (PR) | N (无) | 无需任何身份认证 |
| 用户交互 (UI) | N (无) | 受害者不需要点击任何东西 |
| 作用域 (S) | C (改变) | 从攻击者的源站影响其他域的请求 |
| 机密性影响 (C) | H (高) | Cookie 中的认证令牌/会话信息完全泄漏 |
| 完整性影响 (I) | N (无) | 不直接影响数据完整性 |
| 可用性影响 (A) | N (无) | 不直接影响服务可用性 |
4.3 修复的巨大挑战
curl 的嵌入性带来了特殊的修复困难:
修复传播路径:
curl 8.21.0 发布(2026-06-24)
│
├─→ Linux 发行版包管理器
│ ├── Debian/Ubuntu: apt-get update && apt-get upgrade curl
│ ├── RHEL/CentOS: yum update curl
│ └── Alpine: apk upgrade curl
│ 预计修复覆盖率:数天到数周
│
├─→ macOS (通过 Homebrew 或系统自带)
│ └── brew upgrade curl
│ 预计修复覆盖率:数天
│
├─→ 容器镜像
│ ├── 基础镜像(alpine, debian, ubuntu)需要被重新构建
│ ├── 下游镜像需要 FROM 新的基础镜像
│ └── 预计修复覆盖率:数周到数月
│
├─→ 应用内嵌 libcurl
│ ├── PHP cURL 扩展 → 等待 PHP 发行版更新
│ ├── Python pycurl → pip install --upgrade
│ ├── Go cgo + libcurl → 重新编译
│ └── Node.js node-libcurl → npm update
│ 预计修复覆盖率:数月
│
└─→ IoT / 嵌入式设备
├── 厂商固件更新周期:数月到永不
└── 预计修复覆盖率:极低 (许多设备永远不会更新)五、安全从业者的检测与防御
5.1 如何检测你的系统是否受影响
#!/bin/bash
# 检测系统 curl 版本是否受影响
# CVE-2026-8924 影响 curl 7.46.0 ~ 8.20.0
CURL_VERSION=$(curl --version | head -1 | awk '{print $2}')
echo "当前 curl 版本: $CURL_VERSION"
MIN_AFFECTED="7.46.0"
MAX_AFFECTED="8.20.0"
FIXED="8.21.0"
# 简单的版本比较(完整实现需要更严格的 semver 比较)
version_compare() {
printf '%s\n%s\n' "$1" "$2" | sort -V | head -1
}
if [ "$(version_compare "$CURL_VERSION" "$MIN_AFFECTED")" = "$MIN_AFFECTED" ] && \
[ "$(version_compare "$CURL_VERSION" "$MAX_AFFECTED")" != "$FIXED" ]; then
echo "⚠️ 警告:你的 curl 版本 $CURL_VERSION 受 CVE-2026-8924 影响!"
echo "请升级到 curl >= 8.21.0"
else
echo "✅ 你的 curl 版本 $CURL_VERSION 不受此漏洞影响"
fi5.2 临时缓解措施
在不方便立即升级 curl 的环境中,可以采取以下临时措施:
# 方案 1:在 curl 命令中禁用 Cookie 引擎
curl --cookie /dev/null --no-cookies https://example.com
# 方案 2:在应用代码中过滤带尾随点的 Domain
# Python 示例
import http.cookiejar
import re
class SafeCookiePolicy(http.cookiejar.DefaultCookiePolicy):
def set_ok_domain(self, cookie, request):
# 拒绝 domain 以点结尾的 Cookie
if cookie.domain and cookie.domain.endswith('.'):
return False
return super().set_ok_domain(cookie, request)
# Go 示例
func sanitizeCookieDomain(domain string) string {
return strings.TrimRight(domain, ".")
}5.3 入侵检测规则
# Suricata / Snort 规则:检测 CVE-2026-8924 攻击流量
# 检测 HTTP 响应中带尾随点 Domain 的 Set-Cookie 头
alert http $EXTERNAL_NET any -> $HOME_NET any (
msg: "CVE-2026-8924 curl Super Cookie Injection - Set-Cookie with trailing dot domain";
flow: to_client, established;
content: "Set-Cookie";
http_header;
content: "Domain=";
http_header;
pcre: "/Domain=[^;]*\.[\x0d\x0a;]/i";
classtype: attempted-user;
sid: 1000001;
rev: 1;
)
# 检测对带尾随点域名的 HTTP 请求(可能是攻击的前置步骤)
alert http $HOME_NET any -> $EXTERNAL_NET any (
msg: "CVE-2026-8924 Request to suspicious trailing-dot domain";
flow: to_server, established;
http_host;
pcre: "/Host\x3a\x20[^\r\n]*\.[\x0d]/i";
classtype: suspicious-traffic;
sid: 1000002;
rev: 1;
)5.4 Cookie Jar 清理脚本
#!/usr/bin/env python3
"""
清理 curl cookie jar 中可能被污染的超级 Cookie
用法:python clean_cookie_jar.py ~/.curl_cookies.txt
"""
import sys
from pathlib import Path
# 已知的公共后缀(简化版,实际应使用完整 PSL)
PUBLIC_SUFFIXES = {
'com', 'org', 'net', 'edu', 'gov', 'mil',
'co.uk', 'ac.uk', 'gov.uk',
'com.cn', 'org.cn', 'net.cn',
'co.jp', 'or.jp', 'ne.jp',
'com.au', 'net.au', 'org.au',
'github.io', 'gitlab.io', 'pages.dev',
'compute.amazonaws.com',
# ... 实际应加载完整 PSL 列表
}
def is_public_suffix(domain: str) -> bool:
"""检查 domain 是否为公共后缀(带或不带尾随点)"""
# 去掉尾随点
domain = domain.rstrip('.')
# 去掉前导点(libcurl 内部可能添加)
domain = domain.lstrip('.')
return domain.lower() in PUBLIC_SUFFIXES
def clean_cookie_jar(jar_path: str) -> tuple[int, int]:
"""清理 cookie jar,移除可疑条目"""
path = Path(jar_path)
if not path.exists():
print(f"Error: {jar_path} not found")
return 0, 0
lines = path.read_text().split('\n')
clean_lines = []
removed = 0
kept = 0
for line in lines:
line = line.strip()
# 跳过注释和空行
if not line or line.startswith('#'):
clean_lines.append(line)
continue
# Cookie jar 格式:domain\tflag\tpath\tsecure\texpires\tname\tvalue
parts = line.split('\t')
if len(parts) < 7:
clean_lines.append(line)
continue
domain = parts[0]
# 检查是否为超级 Cookie
if is_public_suffix(domain):
print(f"[REMOVED] domain={domain}, name={parts[5]}")
removed += 1
else:
clean_lines.append(line)
kept += 1
# 写回清理后的内容
path.write_text('\n'.join(clean_lines) + '\n')
return removed, kept
if __name__ == "__main__":
jar_path = sys.argv[1] if len(sys.argv) > 1 else "~/.curl_cookies.txt"
removed, kept = clean_cookie_jar(jar_path)
print(f"\n清理完成:移除 {removed} 个可疑 Cookie,保留 {kept} 个正常条目")六、总结与教训
CVE-2026-8924 是一个教科书级别的"输入规范化"安全漏洞:
- 看似无害的差异酿成大祸:一个尾随点(
.),在 DNS 中是合法的,在 PSL 中不存在,这个微小的不匹配导致了整个安全机制的失效 - 安全边界的输入验证不容忽视:
is_public_suffix()是 Cookie 安全的最后一道防线,但这个防线假设输入已经被规范化——这个假设在带尾随点的 URL 场景下不成立 - 嵌入式依赖的修复困境:curl 的广泛嵌入性使得修复传播极为困难,IoT 设备可能永远无法修复
- cookie jar 的持久化风险:被污染的 Cookie 可能长期存在于
.curlrc或cookies.txt中,即使升级了 curl,旧的 Cookie jar 仍需手动清理
对于安全从业者,这个漏洞提醒我们:安全机制之间的假设不一致往往是漏洞的高发地带——PSL 假设域名已经规范化,但 HTTP 协议允许带尾随点的域名,而 curl 没有在这两者之间做衔接。

