Sec-Fetch-Mode: no-cors)绕过 XSRF严重程度:中危(CVSS 5.4)
CWE:CWE-352 — 跨站请求伪造(XSRF)
受影响版本:jupyterhub 4.1.0 ≤ 版本 < 5.4.5(已在 5.4.5 中修复)
安全公告:GHSA-m68r-v472-jgq9
NVD:https://nvd.nist.gov/vuln/detail/CVE-2026-40864
致谢:Romain Deperne
JupyterHub 的 XSRF 防护(在 4.1.0 中重构)使用 Sec-Fetch-Mode 请求头来判断请求是否同源。它将 Sec-Fetch-Mode: no-cors 视为同源——但 no-cors 恰恰是浏览器在跨域“简单”表单提交时发送的值。因此,向 Hub 表单端点(/hub/spawn、/hub/accept-share)发起的跨域 HTML 表单 POST 请求完全绕过了 XSRF 检查。
JSON API 不受影响(它要求非简单内容类型,这会强制触发 CORS 预检)。只有 HTML 表单端点可以通过这种方式访问。
XSRF 逻辑对一组旨在捕获同源导航的 Sec-Fetch-* 状态直接短路判定为“可信”。Sec-Fetch-Mode: no-cors 被包含在该可信集合中。但 no-cors 是浏览器为向不同源提交的普通 <form method=POST> 所分配的模式——这正是令牌本应阻止的经典 CSRF 攻击向量。因此,任何接受简单表单体且仅依赖此 XSRF 门控的状态变更端点,都可以被跨域伪造。
将其映射到实际端点:/hub/spawn(启动受害者的服务器)和 /hub/accept-share(使受害者接受攻击者服务器的共享)都是位于此门控之后的表单 POST。
/hub/spawn — 攻击者页面可以在未经同意的情况下启动受害者的单用户服务器(资源消耗 / 意外状态;攻击者不会获得对该服务器的访问权限)。/hub/accept-share — 当攻击者是获准共享自己服务器的 JupyterHub 用户时,他们可以强制受害者接受共享,从而使受害者获得对攻击者服务器的访问权限(这是进一步社会工程 / 数据投放场景的铺垫步骤)。将 Sec-Fetch-Mode 用作来源判定依据是不可靠的:no-cors 并不意味着同源。5.4.5 中的修复不再将 no-cors 视为同源。无法立即升级的运维人员可以在反向代理处丢弃携带 Sec-Fetch-Mode: no-cors 的请求。
poc/csrf_spawn.html — 将其托管在任意攻击者源上,让已登录的 JupyterHub 用户打开它。自动提交的表单会向 /hub/spawn 发起跨域 POST(浏览器发送 Sec-Fetch-Mode: no-cors);存在漏洞的版本会在没有有效 _xsrf 令牌的情况下接受该请求并启动受害者的服务器。将 action 指向 /hub/accept-share 即可实现接受共享的变体。
1. 编辑 poc/csrf_spawn.html 中的 TARGET-JUPYTERHUB
2. 从攻击者源提供该文件(任意静态托管)
3. 在已通过目标 Hub 身份验证的浏览器中打开它
4. 观察受害者的服务器在未提供 XSRF 令牌的情况下被启动
已负责任地披露。PoC 在修复发布后公开。