
Proof-of-concept for CVE-2026-40864: JupyterHub XSRF bypass via cross-origin form POST exploiting Sec-Fetch-Mode: no-cors. Includes PoC HTML, root cause analysis, and disclosure timeline.
Sec-Fetch-Mode: no-cors)严重级别:中等
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 正是浏览器在发送跨源“简单”表单提交时使用的模式。因此,跨源 HTML 表单 POST 到 Hub 的表单端点(/hub/spawn、/hub/accept-share)完全绕过了 XSRF 检查。
JSON API 不受影响(它要求非简单的 Content-Type,从而触发 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。