主题
字号
CHAPTER 07 ≈ 20 MIN READ

PDF 不只能看:交互、表单与安全

7.1 PDF 中的 JavaScript

7.1.1 是的,PDF 能跑代码

很多人不知道这一点:PDF 从 1.3 版本(1999 年)开始支持嵌入 JavaScript

这不是"类 JavaScript 的脚本"——它是标准的 ECMAScript,加上 Adobe 定义的一套 PDF 专用 API。你可以在 PDF 中:

一段 PDF 内嵌 JavaScript 的示例:

// 当"数量"字段改变时,自动计算总价
var quantity = this.getField("quantity").value;
var price = this.getField("unitPrice").value;
var total = quantity * price;
this.getField("totalPrice").value = total.toFixed(2);

7.1.2 JavaScript 在 PDF 中的位置

JavaScript 通过以下方式嵌入 PDF:

% 文档级 JavaScript(打开 PDF 时执行)
<< /Type /Catalog
   /Names << /JavaScript << /Names [(init) 10 0 R] >> >>
>>

10 0 obj
<< /S /JavaScript /JS (app.alert\("Hello from PDF!"\)) >>
endobj

触发时机:

7.1.3 安全隐患

一份看似无害的 PDF 文档可以在你打开它的瞬间执行任意 JavaScript 代码。虽然 PDF 的 JS 引擎有沙箱限制(不能直接读写本地文件系统),但历史上出现过大量沙箱逃逸漏洞:

年份 CVE 影响
2009 CVE-2009-0927 Acrobat JS heap overflow,远程代码执行
2010 CVE-2010-0188 TIFF 解析 + JS 组合攻击
2013 CVE-2013-0640 Acrobat 沙箱逃逸
2018 CVE-2018-4990 Acrobat JS UAF 漏洞
2023 CVE-2023-26369 Acrobat out-of-bounds write

这就是为什么安全专家建议:不要用 Adobe Acrobat 打开来源不明的 PDF。用浏览器(有额外沙箱层)或 Sumatra(根本不执行 JS)更安全。


7.2 表单系统

7.2.1 AcroForm:PDF 原生表单

PDF 1.2 引入了 AcroForm——一套内置的交互式表单系统。表单字段直接嵌入在 PDF 对象结构中:

<< /Type /Annot
   /Subtype /Widget
   /FT /Tx                    % 文本输入框
   /T (username)              % 字段名
   /V (John Doe)              % 当前值
   /Rect [100 700 300 720]    % 位置和大小
   /DA (/Helvetica 12 Tf 0 g)  % 默认外观
>>

支持的字段类型:

AcroForm 的优势是简单且所有 PDF 阅读器都支持。缺点是布局能力有限——字段的位置和大小是固定的坐标值,不能响应式适配。

7.2.2 XFA:Adobe 的"重型"表单

**XFA(XML Forms Architecture)**是 Adobe 在 2003 年推出的另一套表单系统——比 AcroForm 强大得多,但也复杂得多。

XFA 用 XML 描述表单结构,支持:

但 XFA 的问题:

  1. 只有 Acrobat 完整支持:其他阅读器(包括 Chrome、Firefox、Sumatra)不支持或仅部分支持
  2. PDF 2.0 已弃用 XFA:ISO 在 PDF 2.0 规范中移除了 XFA,宣告其终结
  3. 安全性差:XFA 的复杂性带来了大量攻击面

尽管被弃用,大量政府和企业表单仍然使用 XFA 格式——因为它们是十年前创建的,没有人愿意花钱重做。

7.2.3 为什么政府至今离不开 PDF 表单

税务申报、签证申请、法院文件、医疗记录……全世界的官僚机构都依赖 PDF 表单。原因是:

  1. 格式固定:一份表单的布局不会因为接收端的软件版本而改变。政府需要标准化的纸面格式。
  2. 可打印:填完表单后可以直接打印,格式和空白表单完全对齐。
  3. 数字签名:PDF 支持符合法律效力的数字签名,很多国家的电子签名法认可 PDF 签名。
  4. 离线使用:不依赖网络连接,这在很多场景下仍然必要。
  5. 存档可靠:PDF/A 标准保证文件在几十年后仍能正确打开。

Web 表单(HTML form)虽然更现代、更友好,但在法律、存档和离线等维度上仍然无法完全替代 PDF 表单。


7.3 数字签名

7.3.1 PDF 签名的原理

PDF 数字签名的核心是 **PKCS#7(CMS)**格式的加密签名,嵌入在一个签名字段中:

签名流程:
1. 计算 PDF 文件特定字节范围的哈希(SHA-256)
2. 用签名者的私钥加密该哈希 → 得到签名值
3. 把签名值 + 签名者证书 + 时间戳嵌入 PDF

验证流程:

1. 用签名者的公钥(从证书中提取)解密签名值 → 得到原始哈希
2. 重新计算同一字节范围的哈希
3. 比较两个哈希——一致则签名有效
4. 验证证书链:签名者证书 → 中间 CA → 根 CA
5. 检查证书是否已被吊销(CRL/OCSP)

7.3.2 增量保存与签名的配合

还记得上一章讲的增量保存吗?它和数字签名配合得很好:

签名覆盖的字节范围是原文件内容(不包括签名值本身的占位空间)。之后的增量保存追加在文件末尾,不改变已签名的区域。这意味着:

但这也带来了"Shadow Attack"风险——攻击者可以在签名前就埋入隐藏内容(通过增量保存覆盖显示),签名后再通过删除增量来"揭露"隐藏内容,而签名仍然显示有效。2020 年的研究表明主流 PDF 阅读器中有多个存在此类漏洞。

7.3.3 法律效力

不同国家/地区对 PDF 数字签名的法律认可程度不同:


7.4 加密与权限控制

7.4.1 PDF 加密的两种密码

PDF 文件可以设置两种密码:

权限控制的种类:

/P -3904    % 权限标志(位掩码)
权限位 控制内容
bit 3 打印
bit 4 修改内容
bit 5 提取文字/图片
bit 6 添加/修改注释和表单
bit 9 填写表单
bit 10 无障碍提取(屏幕阅读器)
bit 11 组装文档(插入/删除页面)
bit 12 高质量打印

7.4.2 加密算法的演进

PDF 版本 算法 密钥长度 安全性(2026 年)
1.1-1.3 RC4 40 bit ❌ 秒破
1.4 RC4 128 bit ⚠️ 已不推荐
1.5 RC4 128 bit ⚠️ 已不推荐
1.6 AES 128 bit ✓ 尚可
1.7+ AES 256 bit ✓ 安全
2.0 AES-256 (ISO 32000-2) 256 bit ✓ 安全

重要的现实:Owner Password 只是一个"君子协定"。技术上,PDF 内容必须能被解密才能渲染——所以加密密钥实际上嵌入在文件中(用一定方式混淆)。有 Owner Password 保护但没有 User Password 的 PDF,其内容对任何有技术能力的人来说是完全可读的。很多开源工具(如 qpdf)可以直接去除 Owner Password 限制。

# 去除 PDF 的限制(如果没有 User Password)
qpdf --decrypt protected.pdf unlocked.pdf

User Password 则不同——它直接参与加密密钥的推导。不知道密码就真的无法解密内容(假设使用了 AES-256)。


7.5 PDF 安全攻击简史

7.5.1 PDF 作为攻击载体

PDF 是恶意软件最常用的载体之一。原因:

  1. 格式复杂:规范 1000+ 页,实现中必然有 bug
  2. 功能丰富:JS 引擎、字体解析、图片解码——每个都是独立的攻击面
  3. 用户信任:人们不像对 .exe 那样警惕 .pdf 文件
  4. 商业目标:企业和政府大量使用 PDF,攻击者的投入回报率高

7.5.2 经典攻击类型

1. JavaScript 攻击

// 恶意 PDF 中的 JS(简化示例)
var shellcode = unescape("%u9090%u9090...");
var spray = "";
while (spray.length < 0x40000) spray += shellcode;
// 触发堆溢出漏洞,跳转到 shellcode

通过 JS 精确控制内存布局(堆喷射),再触发渲染引擎的某个漏洞来获得代码执行。

2. 字体解析漏洞

字体文件(尤其是 Type 1 和 CFF)的解析器代码复杂且古老。畸形的字体数据可以触发缓冲区溢出。

3. 图片解码漏洞

JBIG2、JPEG 2000 等图片格式的解码器中的漏洞。2021 年 Apple 的 FORCEDENTRY 零点击漏洞就利用了 CoreGraphics 中的 JBIG2 解析器——恶意 PDF 通过 iMessage 发送,无需用户交互即可入侵 iPhone。

4. 行为攻击(无需漏洞)

7.5.3 防护措施

现代 PDF 阅读器的安全策略:

阅读器 策略
Chrome PDFium 运行在独立沙箱进程中,几乎无法逃逸
Firefox PDF.js JS 实现 → 无原生代码漏洞,天然安全
Adobe Acrobat Protected Mode 沙箱 + 禁止自动执行高危操作
Sumatra 不执行 JS、不支持表单提交、不加载外部资源

最安全的做法:用浏览器打开不信任的 PDF。浏览器的沙箱是经过十几年安全对抗锤炼出来的,比 PDF 阅读器的沙箱成熟得多。


7.6 PDF/A:为永久保存而设计

7.6.1 什么是 PDF/A

PDF/A 是 PDF 的存档子集(A = Archive)。它限制了 PDF 的功能,确保文件在几十年后仍然能被正确打开和渲染:

PDF/A 禁止的特性:

PDF/A 要求的特性:

7.6.2 PDF/A 的级别

级别 基于 关键限制/改进
PDF/A-1a PDF 1.4 完整 Tagged + Unicode 映射
PDF/A-1b PDF 1.4 仅视觉保真(无结构要求)
PDF/A-2a/b/u PDF 1.7 允许 JPEG 2000、透明度、层
PDF/A-3a/b/u PDF 1.7 允许嵌入任意文件附件
PDF/A-4 PDF 2.0 最新版本,简化合规级别

图书馆、档案馆、法院、医院——所有需要长期保存文档的机构都使用 PDF/A。德国法律要求电子发票以 PDF/A-3 格式存档(ZUGFeRD 标准)。