CVE-2026-47265 — aiohttp 跨域重定向时泄露请求级 Cookie¶
漏洞披露
通过 cookies= 关键字参数传递给 aiohttp.ClientSession 的请求级 Cookie,在发生跨域重定向后会被转发给外部域,从而泄露仅应发送至原始目标的凭证。
| 字段 | 值 |
|---|---|
| 项目 | aiohttp |
| 受影响组件 | aio-libs/aiohttp — ClientSession 请求级 Cookie |
| 严重级别 | MEDIUM |
| CVSS 4.0 | 6.6 Medium (AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N) |
| CWE | CWE-346 — Origin Validation Error |
| 受影响版本 | < 3.14.0 |
| 修复版本 | 3.14.0(发布于 2026-06-01) |
| 公告 | GHSA-hg6j-4rv6-33pg / CVE-2026-47265 |
1. 漏洞概述¶
aiohttp 是 Python 生态中常用的异步 HTTP 客户端/服务端框架。当应用程序通过 cookies= 关键字参数向 ClientSession.get()、.post() 或底层 ._request() 方法传递请求级 Cookie,而服务器返回跨域重定向响应时,这些 Cookie 会被静默地转发给外部目标域。攻击者只要能够影响重定向目标——例如利用首跳服务器上的开放重定向、SSRF 漏洞或被入侵的 CDN 边缘节点——就能在重定向请求的 Cookie 头中收到受害方的请求级 Cookie 值。
存储在 CookieJar 中的会话级 Cookie 不受此漏洞影响,Cookie Jar 的域名过滤机制会正确拦截跨域请求。仅 cookies= 请求级参数受影响。
根本原因¶
文件: aiohttp/client.py
函数: ClientSession._request — while True 重定向循环(v3.13.5 约第 637 行)
重定向循环每次迭代时,该方法都会重建 all_cookies 并重新附加所有请求级 Cookie:
# Line 690-699 — runs on every loop iteration, including after a redirect
all_cookies = self._cookie_jar.filter_cookies(url)
if cookies is not None: # <-- cookies still set after cross-origin redirect
tmp_cookie_jar = CookieJar(
quote_cookie=self._cookie_jar.quote_cookie
)
tmp_cookie_jar.update_cookies(cookies)
req_cookies = tmp_cookie_jar.filter_cookies(url)
if req_cookies:
all_cookies.load(req_cookies) # <-- attacker-controlled origin receives cookies
跨域重定向时,代码会清除 auth、headers[AUTHORIZATION]、headers[COOKIE] 和 headers[PROXY_AUTHORIZATION],但从未重置 cookies 变量:
# Line 893-897 — cross-origin redirect detected
if url.origin() != redirect_origin:
auth = None
headers.pop(hdrs.AUTHORIZATION, None)
headers.pop(hdrs.COOKIE, None)
headers.pop(hdrs.PROXY_AUTHORIZATION, None)
# BUG: cookies = None is missing here
cookies 未被清除,下一次迭代依然进入 if cookies is not None: 分支,将请求级 Cookie 附加到发往外部域的请求中。
补丁¶
修复在提交 f54c40851b0d6c4bbdab97ba518a223adda32478(从 d57efb05f5073071ceb2d3b35d72d9d0bc4512a2 挑选合并,PR #12550 "Drop cookies on redirect")中实施,只改了一行:
--- a/aiohttp/client.py
+++ b/aiohttp/client.py
@@ -971,6 +971,7 @@ async def _connect_and_send_request(
if url.origin() != redirect_origin:
auth = None
+ cookies = None
headers.pop(hdrs.AUTHORIZATION, None)
headers.pop(hdrs.COOKIE, None)
headers.pop(hdrs.PROXY_AUTHORIZATION, None)
cookies 置为 None 后,下一次循环迭代中的 if cookies is not None: 判断为假,请求级 Cookie 不再附加到发往外部域的请求中。
2. 漏洞环境¶
复现环境是一个运行在私有桥接网络上的三容器 Docker Compose 栈。三个服务共用同一个基于 python:3.12-slim-bookworm 构建的镜像,漏洞版本的 aiohttp 通过构建参数固定。
下载所有环境文件:env.zip
单个文件: - env/Dockerfile - env/docker-compose.yml - env/config/victim_entrypoint.sh - env/config/victim_app.py - env/config/firsthop_app.py - env/config/collector_app.py
拓扑结构¶
| 容器 | 角色 | 网络 | 发布端口 |
|---|---|---|---|
cve-2026-47265-victim |
漏洞版本 aiohttp 3.13.5 客户端;启动时生成新鲜密钥,并暴露 /trigger 控制端点 |
lab 桥接网络 |
127.0.0.1:9000 → 9000 |
cve-2026-47265-firsthop |
源域 A — 请求级 Cookie 的合法作用域;/redirect 发出跨域 302 重定向至 collector |
lab 桥接网络 |
仅内部 |
cve-2026-47265-collector |
外部域 B — 将每个入站 Cookie 头记录到 /loot/cookie_header.log |
lab 桥接网络 |
仅内部 |
受害客户端请求 http://firsthop:80/redirect,firsthop 响应 302 Location: http://collector:80/collect。两个主机名不同,aiohttp 将 url.origin() != redirect_origin 评估为真,进入存在漏洞的分支。
启动环境¶
确认存在漏洞的版本¶
容器健康后,验证固定版本号及新鲜密钥、空战利品文件是否就位:
curl -s http://127.0.0.1:9000/health \
&& docker exec cve-2026-47265-victim sh -c 'test -s /secret/cookie_value && echo secret-present' \
&& docker exec cve-2026-47265-collector sh -c 'test -f /loot/cookie_header.log && echo loot-ready'
预期输出:ok、secret-present、loot-ready。
在受害容器内检查固定的 aiohttp 版本:
预期输出:Version: 3.13.5。
受害容器内 /secret/cookie_value 中的密钥是由 victim_entrypoint.sh 在每次容器启动时生成的随机 UUID 十六进制字符串,未烘焙到镜像中,每次重启后都会改变。
3. 漏洞利用方法¶
漏洞利用通过一个 shell 脚本(exploit/run.sh)完成:脚本向受害方的触发端点发起请求,然后读取 collector 记录的入站 Cookie 头。
下载利用脚本:exploit.zip
前置条件¶
开始前验证环境健康且战利品文件为空:
curl -s http://127.0.0.1:9000/health \
&& docker exec cve-2026-47265-victim sh -c 'test -s /secret/cookie_value && echo secret-present' \
&& docker exec cve-2026-47265-collector sh -c 'test -f /loot/cookie_header.log && echo loot-ready'
第一步 — 记录真实密钥¶
从受害方的受保护存储中读取新鲜密钥,这一值不应由利用脚本自身提供:
在已验证的运行中,该命令返回 691e6dd64d84498595e22bf87cb860c3。
第二步 — 执行利用¶
脚本通过 curl 调用受害方的 /trigger 端点。受害方的 aiohttp 客户端随即以 cookies={"session": <secret>} 向 http://firsthop:80/redirect 发出 GET 请求。firsthop 返回 302 Location: http://collector:80/collect。由于跨域重定向时 cookies 从未被清除,存在漏洞的客户端将请求级 Cookie 重新附加到发往 collector 域的后续请求上。
第三步 — 观察泄露的 Cookie¶
脚本打印 collector 记录的入站 Cookie 头。在已验证的运行中,输出如下:
[*] Triggering victim first-hop request -> cross-origin redirect
victim issued first-hop request with a per-request cookie; final response from chain: 'collected'
[*] Trigger returned; reading foreign-origin (collector) Cookie-header record
=== collector inbound Cookie header(s) (cross-origin observation point) ===
session=691e6dd64d84498595e22bf87cb860c3
=== end ===
证明漏洞已触发¶
证据来自独立观测,而非利用脚本自身的输出。验证通过特权通道直接进行:对 collector 容器执行 docker exec 读取战利品文件,再以同样方式读取受害方的真实密钥进行比对:
docker exec cve-2026-47265-victim cat /secret/cookie_value
# -> 691e6dd64d84498595e22bf87cb860c3
docker exec cve-2026-47265-collector cat /loot/cookie_header.log
# -> session=691e6dd64d84498595e22bf87cb860c3
两个值一致:受害方受保护存储中的 691e6dd64d84498595e22bf87cb860c3 与 collector 记录的入站 Cookie 头完全相同。利用脚本从未接触过该密钥,它只调用了 /trigger 并读取了 collector 从自身入站 HTTP 头写入的文件。原本作用于源域 A(firsthop)的密钥逐字出现在无关的外部域 B(collector)中,这只有在存在漏洞的重定向分支将请求级 Cookie 转发出去时才可能发生。
重启受害容器并观察新值,可确认密钥的新鲜性:
S1 != S2 证明密钥不可预测,无法被猜测或重放利用。
环境清理¶
完成后,停止并删除所有容器和卷:
4. 安全建议¶
修复方案¶
将 aiohttp 升级至 3.14.0 或更高版本。修复在 ClientSession._request 的跨域重定向分支中增加了一行 cookies = None,使后续迭代中的 if cookies is not None: 判断为假,请求级 Cookie 不再附加到外部域请求中。
缓解措施 / 临时方案¶
无法立即升级时,可改为在 headers= 字典中显式传递 Cookie 头。跨域重定向逻辑会清除 headers[COOKIE],因此该路径在所有受影响版本中均安全:
# Safe in all versions — the Cookie header is explicitly cleared on cross-origin redirect
await session.get(url, headers={"Cookie": "session=SECRET"})
# Vulnerable in aiohttp < 3.14.0 — cookies= kwarg is NOT cleared
await session.get(url, cookies={"session": "SECRET"})
此临时方案需要手动拼接 Cookie 字符串,放弃了 cookies= API 的便利性,请根据实际场景权衡。
参考资料¶
- GHSA-hg6j-4rv6-33pg 安全公告 — 主要公告;包含摘要、影响范围、临时方案和补丁链接
- GitHub 公告 API JSON — 受影响版本(
< 3.14.0)、首个修复版本(3.14.0)、CWE-346、CVSS 4.0 评分 6.6 - 补丁提交 f54c408 — 单行修复(
cookies = None),从d57efb05挑选合并至 3.x 稳定分支 - 原始 PR #12550 — "Drop cookies on redirect",由 Sam Bull(Dreamsorcerer)提交
- NVD CVE-2026-47265 — NVD 条目,确认 CWE-346,CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N(6.6 Medium)
- aiohttp v3.14.0 发布页 — 发布日期 2026-06-01,确认修复版本
- aiohttp 更新日志 — 3.14.0 条目:"Fixed per-request
cookiesnot being dropped on cross-origin redirects"(issue #12550)