Security: restore Jupyter origin and XSRF protection - #654
Conversation
|
评审任务执行失败。 |
|
已检查最新评论与 Review 状态。当前唯一评论是“评审任务执行失败”,没有具体代码或测试修订意见,因此没有制造无意义的代码变更或空提交。 PR 中的 Jupyter Origin/XSRF 修复及回归测试保持不变,现有 CI 检查均通过;如需要重新执行自动评审,可直接重新触发该 PR 的 Review。 |
|
@monkeyscan review pr |
|
PR Title: Security: restore Jupyter origin and XSRF protecti... Commit: 本次改动为 Jupyter 安全加固:将 guest Jupyter 启动命令从 审查评估:改动范围小、意图清晰、方向正确。前台 |
winterfx
left a comment
There was a problem hiding this comment.
结论
安全问题是真的,方向也对,但这个改动单独合并会让经代理访问的 JupyterLab 不可用。建议连同代理侧一行改动一起合。
我用 docker driver 起了真实沙箱做了三组对照实测(daemon 127.0.0.1:7410,guest Jupyter 发布在 127.0.0.1:32769)。
1. 漏洞确实存在,可复现
当前 main,通过代理模拟跨站攻击:
POST /jupyter/<sandbox>/api/kernels
Origin: http://evil.example
Cookie: <受害者会话 cookie> # 不带 _xsrf
→ 201 Created
跨站页面仅凭受害者 cookie 就能创建 kernel,等价于任意代码执行。本 PR 要修的问题成立。
2. 但只改 guest 启动参数会打断 JupyterLab
打上本 PR 后,同一套流程里完全正常的浏览器请求开始失败:
| 请求 | main | 本 PR |
|---|---|---|
GET /lab?token=… |
200 | 200 |
GET /api/kernelspecs(cookie,无 Origin) |
200 | 200 |
POST /api/kernels(cookie + 同源 Origin + _xsrf) |
201 | 404 |
| kernel WebSocket(cookie + Origin) | 101 | 403 |
guest 内 Jupyter 自己打的日志:
[W] Blocking Cross Origin API request for /jupyter/…/api/kernels.
Origin: http://127.0.0.1:7410, Host: 127.0.0.1:32769
[W] 404 POST /jupyter/…/api/kernels
[W] Blocking Cross Origin WebSocket Attempt. Origin: http://127.0.0.1:7410, Host: 127.0.0.1:32769
[W] 403 GET /jupyter/…/api/kernels/…/channels
表现是 Lab 页面能打开,但建不了 kernel、连不上 websocket,实际不可用。
根因在代理侧 pkg/agentcompose/proxy/proxy.go:71:
req.Out.Host = target.Host // 重写为 guest 地址 127.0.0.1:32769浏览器的 Origin(http://127.0.0.1:7410)原样透传,于是 Jupyter 看到 Origin != Host。jupyter_server 2.18.2 中 base/handlers.py:445 的绕过分支只对 token 认证的请求生效(auth/identity.py:542 should_check_origin),而首次 ?token= 跳转之后浏览器改用 cookie 认证,绕过失效,APIHandler.prepare 直接 404,websocket 403。
--ServerApp.allow_origin='*' 当初大概率正是为了绕开这个 Host 重写才加上的。
3. 建议:代理侧一并改
// pkg/agentcompose/proxy/proxy.go:71
-req.Out.Host = target.Host
+req.Out.Host = req.In.Host(SetURL 会把 Out.Host 置空,所以必须在其后显式赋值。)Host 透传后 Origin == Host 自然成立,Jupyter 默认同源策略直接通过;连接目标仍由 Out.URL.Host 决定,不受影响。
带上这一行后重测:
| 请求 | 结果 |
|---|---|
GET /lab?token=… / GET /api/kernelspecs |
200 ✅ |
POST /api/kernels(正常浏览器请求) |
201 ✅ |
| kernel WebSocket | 101 ✅ |
Origin: http://evil.example + cookie |
404 🔒 |
同源但无 _xsrf |
403 🔒 |
无 Origin 无 _xsrf |
403 🔒 |
功能完全恢复,且三种攻击形态全部被挡住。go test ./pkg/driver/... ./pkg/agentcompose/proxy/... 通过。
driver 自身的就绪探测(pkg/driver/jupyter_guest.go:74,GET /api/kernelspecs?token=…)走 token 认证绕过路径,两种改法下都不受影响。
4. 关于 CI 全绿
CI 绿不能作为本改动的验证依据:仓库里对 Jupyter 唯一的运行时接触就是上面那条 token 认证的就绪探测,.github/workflows/ 下没有任何浏览器级 e2e。本 PR 新增的断言也只校验启动命令字符串,覆盖不到行为。
5. 测试的小意见
新增断言放进了 TestBackgroundJupyterLaunchCommandStartsBeforeJupyterLabImportProbe(一个测启动顺序的用例),而且是整串精确匹配 --ServerApp.allow_origin='' --ServerApp.disable_check_xsrf=False,参数顺序一动就红。建议拆成独立测试,两个 flag 分别断言。
|
已按最新评论完成修订:
本地验证:
|
问题
Jupyter guest 启动参数设置了
--ServerApp.allow_origin='*'和--ServerApp.disable_check_xsrf=True。同时 daemon 的 Jupyter 代理路径不使用 daemon Bearer Token,导致泄露 Jupyter URL Token 后缺少 Origin/XSRF 保护层。影响
恶意站点或被动攻击者在获得 Token 后,可跨站调用 Jupyter API、访问文件或操作 Kernel。涉及 CWE-352、CWE-942。
修复内容
--ServerApp.disable_check_xsrf=False。验证
go test ./pkg/driver ./pkg/agentcompose/proxygit diff --check