next/og ImageResponse SVG 注入 (GHSA-vcvr-r3jv-pc5j)用于在隔离的、仅限回环的实验室环境中复现和验证 CVE-2026-94545 的安全研究材料,
即 Next.js ImageResponse SVG 序列化注入(上游:Satori
GHSA-wx4j-mvgx-mqwp)。
>=16.2.0 <16.3.6(仅限 Node.js ImageResponse)26a52aff);15.5.26 包含
相关加固,15.x 不受 RCE 问题影响仅可在您拥有或明确授权测试的系统上使用。 该 扫描器是非破坏性的:它渲染图像并统计像素。不会在目标上 写入、存储或执行任何内容。
状态:在所提供的实验室中已证实。
针对公告中确切的路径模式(攻击者值进入
<svg><title>{value}</title></svg>):
next 16.3.5 (vulnerable): injected 500x150 red rect rendered into the PNG
(75,000 #FF0000 pixels measured in the response)
next 16.3.6 (patched) : value escaped, 0 red pixels
satori 0.25.0 (vuln) : payload present raw in the serialized SVG string
satori 0.33.5 (fixed) : payload escaped to </title>
这端到端地证明了注入原语(序列化器 -> 嵌入的 SVG -> 栅格化输出)。它并未演示远程代码执行:公告将 RCE 与 下游 SVG 解析器关联,而该解析器的漏洞细节在撰写本文时 尚未公开。未声称存在文件读取、SSRF 或代码执行。
Satori 使用 translateSVGNodeToSVGString()
(src/handler/preprocess.ts)序列化 <svg> 节点,该方法在插值到输出 XML 时
未进行转义:
String(node) 返回 - 未转义(现为 escapeXMLText)。" 可突破。style 值原样插值。&,因此内部 XML
实体在外层 SVG 解析中得以保留。expand.ts 将内部样式属性(前缀 _)传入序列化
样式 - 现已被拒绝。该修复(satori@26a52aff)
将所有内容路由到加固的 buildXMLString(),该函数会转义值
并验证 XML 名称。本实验室从行为上验证了所有这些内容;确切的
请求和像素证据位于 poc/ 中。
要求:Docker 及 Compose v2,主机上安装 Python 3.10+(用于 扫描器),首次运行时需能访问 npm。
./lab verify
构建易受攻击的应用(从 [email protected] 引入的 next/og),要求
标记矩形在 HTTP 响应中渲染,然后构建已修补的应用
([email protected])并要求其不存在。
易受攻击的对照:
[*] #FF0000 pixels: 75000 (threshold 10000)
[!!!] INJECTION CONFIRMED: the attacker value escaped <title> and
became a rendered SVG element in the generated image.
RESULT: VULNERABLE CONTROL CONFIRMED
已修补的对照:
[*] #FF0000 pixels: 0 (threshold 10000)
[=] no injection observed
RESULT: PATCHED CONTROL CONFIRMED
./lab start vulnerable # 16.3.5 app on http://127.0.0.1:9481/og
./lab test # assert injection renders
./lab start patched # 16.3.6 app
./lab test # assert absence
./lab status
./lab reset
手动运行扫描器(默认回环;授权的 外部目标需要显式覆盖):
python3 poc/scan.py http://127.0.0.1:9481/og
python3 poc/scan.py https://host.example/og --param value \
--allow-authorized-non-loopback [--insecure]
该载荷是平衡的 XML,携带一个 500x150 的纯红色 <rect>。在
易受攻击的端点上,该值会突破 <title>,矩形成为嵌入 SVG 文档中的
真实元素;扫描器统计返回的 PNG 中精确的 #FF0000
像素(预期 75,000;阈值 10,000)。在已修补的
端点上,该值是被转义的文本,不会渲染出红色。该探测不会在目标上
改变任何内容:一次 GET 请求,一次图像渲染。
请注意,载体故意使用 <rect> 而非文本:嵌入 SVG 内的文本
在渲染器中没有可加载的字体,即使被注入
也不可见。
此漏洞的证明是渲染栅格的像素内容,
而 nuclei 匹配器无法解码 PNG。曾构建了一个大小差异模板
但被否决:在已修补的应用上,被转义的载荷仍会渲染(作为
字面惰性文本),因此良性载荷与载荷的大小在已修补系统上
也存在差异,而易受攻击与已修补增量之间的区分是
应用特定的压缩噪声。发布一个会在已修补
系统上触发的匹配器比不发布更糟。请使用 poc/scan.py;它精确评估
响应图像。
| 版本 | 评估 |
|---|---|
| Next.js 16.3.5 (vendored @vercel/og) | 通过 HTTP 端到端复现注入 |
| Next.js 16.3.6 (vendored @vercel/og) | 复现已修补的阴性对照 |
| satori 0.25.0 (npm) | 序列化 SVG 中的原始注入(源码层面) |
| satori 0.33.5 (npm, 2026 年 9 月 22 日) | 已转义(源码层面) |
| Next.js 16.2.0-16.3.4 | 根据公告受影响;未逐一测试 |
| Next.js 15.x | 根据厂商博客,不受 RCE 问题影响 |
.
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── app/
│ └── server.mjs # advisory route pattern over loopback HTTP
└── poc/
└── scan.py # non-destructive scanner (Python stdlib: urllib + zlib)
未提交:assets/(npm 打包的 next tarball + 解压的 bundle)、
node_modules/、.lab-state。
| 声明 | 状态 |
|---|---|
| satori < 修复版本中的 SVG 序列化注入 | 已证实(源码 + 行为) |
| 通过 next/og 16.3.5 端到端渲染注入的标记 | 已证实 |
| 已修补的阴性对照(16.3.6 / satori 0.33.5) | 已证实 |
| 属性/样式载体(相同根本原因) | 在修复 diff 中确认根本原因;文本载体已证实 |
| 通过注入的资源引用进行 SSRF / 文件读取 | 未测试 |
| 远程代码执行 | 未声称(取决于未公开的下游解析器细节) |
仅可在您拥有或明确授权测试的系统上运行此仓库。
实验室驱动程序拒绝除 vulnerable/patched 之外的变体,
扫描器拒绝非回环目标,除非提供
--allow-authorized-non-loopback,且 Compose 端口绑定
到 127.0.0.1。
有关披露处理,请参阅 SECURITY.md。此仓库 不包含生产目标、客户数据、访问令牌或来自真实系统的 证据。
MIT - 请参阅 LICENSE。