Jupyter Notebook PDF导出漏洞:恶意脚本可导致Windows代码执行

本文详细分析了Jupyter Notebook生态系统中一个高危安全漏洞。在Windows默认环境下,攻击者可通过在笔记本文件夹中放置特定命名的恶意脚本,在用户导出笔记本为PDF时触发任意代码执行,从而完全控制系统。

Jupyter Notebook PDF导出功能中的代码执行漏洞

Imperva 威胁研究团队

Yohann Sillam 发布于 2025年12月16日 阅读时间约 4 分钟

在我们研究了 Cursor 的安全性之后,我们将注意力转向了 Jupyter 生态系统。我们揭示了在默认 Windows 环境中,我们发现的笔记本导出功能中的安全风险,以帮助组织更好地保护其资产和网络。

执行摘要

我们发现了一种新的方式,威胁行为者可以利用外部 Jupyter 笔记本诱骗毫无戒备的用户,并入侵他们的工作站。

建议公司使用集中化的 Jupyter 服务器,保持软件更新,并严格限制可能通过 Jupyter 软件处理的外部文件。

引言

Jupyter notebook 在 AI 项目开发中占有重要地位。早在 2015 年,GitHub 上大约有 20 万个公开的 notebook——到 2021 年初,这个数字已激增至近 1000 万。全球超过 80% 的数据科学家和 AI 工程师都在使用 Jupyter,它已深入 AI 工作流的每一个阶段,从探索性分析和可视化到模型原型设计和协作。

在调查这个生态系统时,我们的方法是尝试设想威胁行为者可能在哪里找到突破口,并利用功能来利用受害者的环境。第一个方向出人意料地简单:配置文件。

配置文件通常被认为是无害的。然而,它们可能包含大多数用户不知道的晦涩参数。忽视它们将是一个严重的错误。

配置文件曾导致许多其他实例中的漏洞。例如,在 VSCode IDE 中,.vscode/settings.json 配置文件也是多个高严重性漏洞发现(CVE-2021-34529、CVE-2025-53773 或 CVE-2025-54130)的关键组成部分。

Jupyter 生态系统的一个特殊性使得这个攻击向量更加有趣,那就是配置文件同时也是完全有效的 Python 可执行文件——这使得它们更容易被利用。

Jupyter 配置文件

最常见的配置文件是 jupyter_notebook_config.py,通常位于用户特定的配置目录(~/.jupyter/)中。它负责定义核心 Notebook 服务器设置,例如网络绑定、身份验证选项、文件系统路径和各种安全相关参数。但是,根据组件不同,也可能使用其他配置文件,例如用于导出设置的 jupyter_nbconvert_config.py,或用于 Jupyter 服务器的 jupyter_server_config.py

配置文件实际上可以存在于任何目录中,允许分层覆盖。可用选项涵盖了广泛的功能,从 UI 行为和身份验证到内核管理、导出格式、日志记录等。这种方法使用户能够对整个 Jupyter 生态系统进行细粒度控制。

例如:

1
2
3
c = get_config()
c.NotebookApp.port = 8888
c.FileContentsManager.save_script = True

然而,考虑到高严重性影响,Jupyter 在 2022 年 10 月决定从配置路径中移除当前工作目录(CWD),从而显著降低了相关风险。

这是我们研究的起点。我们开始寻找类似或更强的方式来利用相同的思路:让一个文件名不受约束的文件与一个 Jupyter notebook 相邻,假设一个毫无戒备的用户会在官方的 Jupyter 软件上对一个完全合法的 Jupyter notebook 触发一个无害的操作,并在无意中允许完整的系统被入侵。

这正是我们通过调查 Jupyter 的官方导出工具 nbconvert 所发现的。

漏洞详情

我们发现的漏洞允许在 Windows 机器上将笔记本导出为 PDF 时执行任意代码。通过在笔记本文件夹位置放置一个命名恰当的恶意脚本,攻击者可以劫持转换过程,并以用户的权限执行代码。

当通过 nbconvert 导出包含 SVG 输出的 Jupyter notebook 时,会触发 svg2pdf.py 预处理器,通过 Inkscape 工具转换 SVG 图像。在此过程中,Inkscape 可执行文件的路径使用 Python 的 shutil.which() 通过以下表达式解析:

1
inkscape_path = which("inkscape")

而没有将 inkscape 作为 nbconvert 的强制依赖项。这为意外的代码执行打开了大门,如下图所示:

图 1:安全问题利用的高级流程图

shutil.which 的行为由内部 Windows API 函数 NeedCurrentDirectoryForExePathW 控制,当未设置 NoDefaultCurrentDirectoryInExePath 环境变量时(这是标准 Windows 安装的默认配置),该函数返回 TRUE(包含当前工作目录)。

在 Python 3.12 之前的版本中,shutil.which() 完全忽略 NoDefaultCurrentDirectoryInExePath 环境变量,使得无法通过配置来阻止这种不安全的搜索行为。

Python 3.12 及更高版本在设置时能正确遵守此环境变量,但该变量在 Windows 系统上默认未设置,使得许多系统仍然易受攻击。

由于 nbconvert 官方支持从 Python 3.9 开始的版本,它包含了两种方式都受此问题影响的版本。

CVE-2025-53000

这种不安全的查找行为与 CWE-427: 不受控制的搜索路径元素 一致。因此,我们建议禁用从当前工作目录搜索 inkscape 软件,并依赖于固定的安全搜索位置。

在收到我们的报告后,Jupyter 团队重现了此问题,承认了相关风险,并申请了 CVE(见下文)。随后就如何修复此问题展开了讨论。然而,Jupyter 团队最终停止了与我们沟通,并且截至今日仍未解决此问题。

此漏洞已被分配编号 CVE-2025-53000。在发布时,维护者尚未发布 GitHub 公告。

由于导出功能被广泛使用且普遍受信任,它对攻击者来说是一个有吸引力的目标,尤其是在笔记本频繁共享的环境中——例如学术研究小组、数据科学团队或教育机构——潜在的利用可能性大大增加。

最终,根据我们 90 天的政策,我们决定发布此公告以帮助保护社区。

演示视频

以下演示视频是在一台使用默认设置的 Windows 10 Enterprise x64 机器上录制的,使用了 miniconda3 和 Python 3.13.9,以及最新的可用 Jupyter 软件版本,包括: Jupyter Core 5.9.1, nbconvert 7.16.6, 以及 Notebook 7.5.0 https://www.imperva.com/blog/wp-content/uploads/sites/9/2025/12/jupyter-nbconvert-poc.mp4

入侵后影响

一旦成功触发,此漏洞使攻击者能够在用户上下文中执行任意代码。这会立即影响机密性、完整性和可用性,因为攻击者可以访问、修改或破坏用户的数据和工作流。在典型的 Windows 数据科学工作站上,受害者账户几乎总是具有:

  • 直接访问敏感笔记本和数据集的能力。
  • 缓存的云凭据(AWS CLI、Azure CLI、gcloud、Databricks 等)。
  • 本地安装的软件包管理器(conda、pip、winget)以及乐于运行额外代码的 DevOps 流水线。

这可能会扩大入侵的影响范围,使其影响扩散到最初的工作站之外。

建议

建议公司依赖集中化的 Jupyter 服务器,确保所有 Jupyter 相关软件保持最新,并对可能通过 Jupyter 工具处理的外部文件实施严格限制。

也建议启用 NoDefaultCurrentDirectoryInExePath 环境变量,以降低从不受信任的位置意外执行文件的风险。

结论

此漏洞表明,如果我们工作流程中那些看不见的“粘合剂”未经过适当审查,就可能成为故障点。

随着工作流程变得更加自动化、可组合和云集成,我们预计在这个快速增长的 AI 生态系统中会出现更多漏洞,我们希望这份报告能鼓励团队更仔细地审视维系其环境的那些默默无闻的依赖项。

时间线

  • 6月8日:提交披露报告。
  • 6月12日:问题被重现。
  • 6月25日:Jupyter 团队申请 CVE 编号。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计