WhatsInMyPDF
← 文章

PDF 里的嵌入 JavaScript 和附件文件到底会做什么

2026年7月27日

PDF 不是一张文档的照片,它是一种容器格式。其中两样它能装下的东西会让人意外:可执行的脚本,以及完整的其它文件。

这两样都是规范里正当的组成部分,都有日常用途,也都值得在你打开一个陌生人发来的文件之前先了解一下,或者在你把一个文件交给自动处理系统之前。

嵌入的 JavaScript

PDF 规范包含一层脚本能力,最初来自 Acrobat。一个文档可以在打开时、显示某一页时、表单字段变化时或者按钮被点击时运行脚本。日常用途是表单:校验一个日期、跨字段计算合计、勾选后启用某一节。含有 JavaScript 的 PDF 里,绝大多数属于这一类,这也是为什么单凭一条 JavaScript 发现,几乎说明不了什么。

真正要紧的是用哪个阅读器打开,因为它们实现的东西并不一样。

浏览器只实现了一个受限的子集。 Firefox 用 pdf.js 渲染 PDF,Chromium 系浏览器用 PDFium。两者都只支持 Acrobat JavaScript API 的一小部分,而且是有意为之:缺失的能力是设计上就不做,不是疏漏,这两个项目都把「任意 Acrobat 脚本在这里跑不起来」当作预期行为而非漏洞。表单脚本能用,更宽的 API 基本不能。

Acrobat 实现的多得多。 完整的 API 远远超出表单算术的范围,而从历史上看,PDF 脚本对攻击者最有用的地方正在这里。这就是「不认识的 PDF 用浏览器打开,别用 Acrobat」这条建议能存活多年的现实理由:不是因为浏览器有什么魔法,而是因为它暴露的面小得多。

阅读器的脚本引擎本身也是一个解析器。 即便是受限的引擎,也仍然是在处理不可信输入的代码。每一个主流阅读器都被发现过与脚本相关的漏洞,企业环境里的标准缓解措施是干脆禁用 PDF 的 JavaScript,而普通读者做的事情几乎没有一件真的需要它。

所以:你用表单模板生成的 PDF 里有 JavaScript,是意料之中。一个没有任何表单字段、不请自来、还希望你用 Acrobat 打开的 PDF 里有 JavaScript,值得再看一眼。

嵌入的文件

PDF 可以在内部携带任意文件,规范里叫嵌入文件流,阅读器通常以附件的形式呈现。它的设计用途是正当的,有时确实有用:报告背后的原始表格、论文配套的数据集、发票的机器可读版本。ZUGFeRD 和 Factur-X 这两个电子发票标准就建立在这个能力上,把一份机器可读的 XML 发票嵌进人类可读的 PDF 里。

在安全上有意义的性质是:嵌入文件是一个藏在另一个文件里旅行的文件。扫描外层文档的东西看到的是一个 PDF,载荷在被提取出来之前不必长得像任何东西,而邮件网关和上传过滤器在容器要拆多深这件事上做法不一。另外,在多数人的收件箱里,PDF 是比 .zip 受欢迎得多的附件,对选择这个手法的人来说,这就是全部理由。

附件不会自己执行任何东西。提取是一个用户动作,运行提取出来的东西是第二个。风险来自一串平常的步骤,而不是一次自动发生的事件,这恰恰是它有效的原因。

怎么判断一条发现

扫描器从结构上报告这两类:这个文件含有 JavaScript,或者这个文件含有嵌入文件,并给出它能读到的文件名。这是关于容器里有什么的陈述,不是关于动机的断言。要读好它,问三个问题。

文档的其余部分能解释它吗? 一个带可填写表单字段又带 JavaScript 的 PDF 是自洽的。一张单页宣传单带一段打开即执行的脚本,不是。

它从哪来? 同一条发现,出现在你自己用开票系统生成的文件里、从 arXiv 下载的论文里、和一个你不认识的地址发来的附件里,含义完全不同。

这次扫描还发现了什么? JavaScript 和隐藏文字加上一句注入措辞同时出现,跟只有 JavaScript 是两份不同的文档。发现之间会叠加,这也是报告把它们分组呈现、而不是压成一个总分的原因。

对一个拿不准的文件该做什么

  • 用浏览器打开,不要用功能齐全的桌面阅读器。在那里能运行的脚本子集小得多。
  • 如果你用 Acrobat,把 PDF 的 JavaScript 关掉。 首选项 → JavaScript → 取消勾选「启用 Acrobat JavaScript」。真正需要它的表单会告诉你。
  • 判断附件之前先把它提取出来,并且把提取出来的东西当成一个独立的、有自己风险的文件,用你扫描任何邮件附件的方式扫描它。
  • 不要依赖嵌入文件的扩展名,那只是一个由制作这个 PDF 的人选定的标签。
  • 保持阅读器更新。 现实世界里多数 PDF 攻陷事件,利用的是已知且已修复的解析器缺陷,而不是什么奇技淫巧。

这些内容的位置

隐藏文字和隐藏指令,关心的是一份文档对读它的机器说了什么。JavaScript 和嵌入文件,关心的是一份文档携带了什么。它们是两个不同的问题,装在同一个容器里到达,而结构扫描两者都报告,因为人真正想问的那个问题「这个文件里有没有我看不见的东西」并不区分它们。

结构扫描覆盖了哪些范围、在哪里是盲的,见文字能藏在 PDF 里的十个地方。要检查具体某个文件,把它拖进扫描器,它在你的浏览器里运行,文件不会被上传。