CVE-2025-23047 — Hubble UI 通配符 CORS 信息泄露¶
发布于 2026-06-06
已验证复现
本漏洞通过无头浏览器跨域读取实验得到确认,可在受影响的 Cilium 版本上成功利用。每次启动时随机生成的密钥由漏洞利用脚本从外部域读取,并通过独立的服务器端特权通道加以确认。
| 字段 | 值 |
|---|---|
| Project | Cilium |
| 受影响组件 | Hubble UI nginx 反向代理(install/kubernetes/cilium/templates/hubble-ui/_nginx.tpl) |
| Severity | MEDIUM |
| CVSS | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N (6.5) |
| CWE | CWE-200 |
| 受影响版本 | v1.14.0–v1.14.18, v1.15.0–v1.15.12, v1.16.0–v1.16.5 |
| 修复版本 | v1.14.19, v1.15.13, v1.16.6 |
| 安全公告 | GHSA-h78m-j95m-5356 |
1. 漏洞概述¶
Cilium 是一个基于 Go 的 Kubernetes CNI 网络项目。其 Hubble UI 组件是用于可视化集群网络流量的浏览器界面,附带一个由 Helm chart 模板生成配置的 nginx 反向代理。在受影响版本中,该模板无条件地向所有 nginx 响应注入 Access-Control-Allow-Origin: * 头部,包括代理 Hubble UI 内部 API 的响应。这使得任何来自外部域的页面都可以向 Hubble UI API 发起跨域请求并读取完整的 JSON 响应。攻击者诱使已认证的 Hubble UI 用户访问攻击者控制的页面后,便可通过受害者浏览器读取集群拓扑信息,包括节点名称、工作负载 IP 地址和网络策略元数据。
根本原因¶
漏洞根源在于 Helm 模板文件 install/kubernetes/cilium/templates/hubble-ui/_nginx.tpl。渲染后,该模板在 nginx 的 server {} 块顶层(即任何 location 块之前)插入以下 CORS 指令:
# CORS
add_header Access-Control-Allow-Methods "GET, POST, PUT, HEAD, DELETE, OPTIONS";
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 1728000;
add_header Access-Control-Expose-Headers content-length,grpc-status,grpc-message;
add_header Access-Control-Allow-Headers range,keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout;
if ($request_method = OPTIONS) {
return 204;
}
# /CORS
/api location 块中单独设置了 proxy_hide_header Access-Control-Allow-Origin;,意图屏蔽来自上游后端的 Access-Control-Allow-Origin 头部。然而,该指令仅对上游响应头生效,对 nginx 自身通过 add_header 添加的头部无效。服务器级别的 add_header Access-Control-Allow-Origin * 已将通配符值写入响应,浏览器因此看到 API 响应中的 Access-Control-Allow-Origin: *,并允许任意域读取响应体。
修复方案(补丁提交 a3489f190ba6e87b5336ee685fb6c80b1270d06d,由 Dmitry Kharitonov 提交,于 2024-11-04 合并)是一个纯删除性变更:完整的 CORS 块和 proxy_hide_header 指令均被删除。不存在任何 Access-Control-Allow-Origin 头部时,浏览器将应用同源策略,阻止对 API 响应的跨域读取。
2. 漏洞复现环境¶
复现环境使用两个 Docker 容器,通过私有桥接网络(env_cve-net)相连。无需搭建 Kubernetes 集群——漏洞存在于渲染后的 nginx 配置中,而非编译后的 Cilium 二进制文件。
下载环境文件:env.zip
| 容器 | 角色 | 域(Origin) |
|---|---|---|
cve-2025-23047-hubble-ui |
存在漏洞的 Hubble UI nginx + 桩 /api 后端(目标,域 A) |
http://127.0.0.1:8081 |
cve-2025-23047-attacker |
托管攻击者页面的静态 Web 服务器(域 B) | http://127.0.0.1:8088 |
hubble-ui 容器运行基于受影响版本 v1.16.5 Helm tag 渲染的 nginx 配置(env/config/nginx.conf.vulnerable),以及一个小型 Python 桩后端(env/config/backend.py),监听回环端口 8090,与真实 Hubble UI 拓扑保持一致。攻击者容器是普通 nginx,从不同端口提供静态 HTML 页面,建立触发浏览器同源策略所必需的不同域。
新鲜密钥机制。 每次容器启动时,入口脚本(env/config/entrypoint.sh)生成一个随机 UUID 并写入容器内的 /run/hubble-secret。桩后端读取该文件,并将其作为 cluster_token 字段包含在每个 /api/* JSON 响应中。该值在构建时不会被硬编码,漏洞利用脚本事先无从得知——只有通过真正成功的跨域浏览器读取,该值才会出现在利用输出中。
启动环境:
关键文件:env/Dockerfile、env/docker-compose.yml、env/config/nginx.conf.vulnerable、env/config/entrypoint.sh、env/config/backend.py。
冒烟测试——确认你拥有存在漏洞的环境:
# 两个容器均在运行:
docker compose -f env/docker-compose.yml ps
# 目标 API 可达;通过服务器端验证通配符头部(仅作佐证):
curl -s -D - http://127.0.0.1:8081/api/cluster | grep -i access-control-allow-origin
# 通过特权通道读取当前真实的新鲜密钥(不经由域 B):
docker exec cve-2025-23047-hubble-ui cat /run/hubble-secret
# 验证攻击者域可达:
curl -s http://127.0.0.1:8088/ | head -1
对 API 端点执行 curl 时,应显示 Access-Control-Allow-Origin: *,这是错误配置的服务器端特征。仅观察该头部属于佐证;证明漏洞真实存在,需要如第 3 节所述的真实跨域浏览器读取。
3. 漏洞利用方法¶
漏洞利用脚本通过 puppeteer-core 驱动真实的无头 Chrome 浏览器,因为该漏洞由浏览器的同源策略机制所执行。curl 等普通 HTTP 客户端会忽略 CORS 头部,无法演示跨域浏览器读取是否真正成功或失败。
下载利用文件:exploit.zip
利用文件:exploit/run.sh、exploit/fetch.js。
步骤¶
第 1 步。 确认环境已启动且目标健康:
预期输出:ok。
第 2 步。 运行漏洞利用脚本——单条 shell 命令,在本地安装 puppeteer-core,随后启动无头 Chrome,将其导航至域 B(http://127.0.0.1:8088),并从该页面向域 A 上的 Hubble UI API 发起跨域 fetch 请求:
bash exploit/run.sh "http://127.0.0.1:8088" "http://127.0.0.1:8081/api/cluster" "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
三个参数分别是:(1)浏览器被导航至的攻击者域,(2)要读取的 Hubble UI API 端点,(3)系统 Chrome 可执行文件的路径(run.sh 直接驱动系统 Chrome,无需额外下载 Chromium)。
第 3 步。 获取打印到 stdout 的 cluster_token 值,并与特权通道值进行比对:
如何证明利用成功¶
验证者通过特权通道(docker exec … cat /run/hubble-secret)独立读取当前真实密钥,这是一条利用脚本本身从未接触过的服务器端路径。这个独立观测将结果归因于真正的跨域浏览器读取,而非任何旁路。
已验证复现运行的实际利用输出:
标准错误(利用进度):
[*] document origin: http://127.0.0.1:8088 (nav status 200 )
[*] cross-origin target: http://127.0.0.1:8081/api/cluster
[*] credentials:include attempt -> blocked: TypeError: Failed to fetch
[+] cross-origin read SUCCEEDED (status 200) from foreign origin http://127.0.0.1:8088 -- body follows:
标准输出(浏览器跨域读取到的 JSON 体):
{"cluster": "hubble-lab", "namespaces": ["kube-system", "default"], "cluster_token": "0538235c-8021-45a0-a42e-fb9335a1f2d6"}
特权通道真实值(独立读取):
利用脚本 stdout 中的 cluster_token(0538235c-8021-45a0-a42e-fb9335a1f2d6)与通过特权通道读取的值完全一致。该密钥在容器启动时新鲜生成(前一次启动产生的是 ab076580-f74e-4779-a924-e085a719fafc,完全不同的值),因此不可能被猜出或预先植入。
注意,stderr 中出现的 credentials:'include' 尝试被有意阻断——CORS 规范规定,当 Access-Control-Allow-Origin 为通配符而非特定域时,不允许携带凭据的跨域读取。利用脚本的判别性步骤是普通跨域 GET,通配符 ACAO 允许该请求,而修复后的版本(完全没有 ACAO 头部)会在浏览器的同源策略执行层将其阻断,导致抛出 TypeError 且不返回任何响应体。
环境清理¶
4. 安全建议¶
修复方案¶
升级至修复版本:v1.14.19、v1.15.13 或 v1.16.6。修复方案删除了 Hubble UI nginx 模板中的整个 CORS add_header 块。不存在任何 Access-Control-Allow-Origin 头部时,浏览器将应用同源策略,拒绝对 API 的跨域读取。
如果无法立即升级,短期缓解措施是手动渲染删除了 CORS 块的 Hubble UI nginx 配置,或在 ingress 或网络策略层面限制 Hubble UI 的访问,防止不受信任域的用户浏览器能够访问它。Hubble UI 不应暴露于公共互联网。
背景说明¶
CVSS 中的 UI:R 条件反映了利用需要诱导已认证的 Hubble UI 用户访问攻击者控制的页面。攻击面因此受限于特定集群中能够访问 Hubble UI 的用户;但所泄露的数据(节点名称、工作负载 IP、命名空间结构)属于敏感的集群拓扑情报,可用于进一步的横向移动规划。
在 /api location 块中使用 proxy_hide_header Access-Control-Allow-Origin 试图屏蔽头部,揭示了一个常见的 nginx 误解:proxy_hide_header 作用于上游响应头部,而非 nginx 自身通过 add_header 设置的头部。使用 nginx CORS 配置的开发者应注意,服务器级别的 add_header 指令会向下传播到嵌套的 location 块,proxy_hide_header 无法覆盖它们。
参考资料¶
- GHSA-h78m-j95m-5356 — 受影响及修复版本、CVSS 向量、CWE 分类、安全公告描述
- NVD CVE-2025-23047 — CWE-200 确认(注意:NVD 列出的修复版本与 GHSA 存在出入,以 GHSA 为准)
- Patch commit a3489f19 — 完整差异,展示
install/kubernetes/cilium/templates/hubble-ui/_nginx.tpl中 CORS 块的删除 - BUseclab/cve-genie exploit.py — 机器生成的 PoC,演示头部存在性检测