Technical Article

内存安全的 PDF 解析:防御恶意文档

PDF 文档功能极其强大,但这种强大伴随着固有的安全风险。由于 PDF 支持嵌入式文件、交互式 JavaScript 和复杂的二进制流,它们经常被用作传输恶意软件的载体。编写糟糕的 PDF 解析器中出现的缓冲区溢出、越界读取和整数溢出可能导致远程代码执行(RCE)。

如果您正在 Delphi 中构建一个接受用户上传的 PDF 的应用程序(例如,文档接收门户),确保内存安全的 PDF 解析是一项关键的安全要求。

常见的 PDF 攻击载体

恶意的 PDF 通常针对解析器本身的漏洞而不是操作系统。常见的技术包括:

  • 格式不良的交叉引用(XRef)表: 伪造导致越界的指针偏移,使解析器崩溃或允许内存泄漏。
  • 无限循环: 在 PDF 对象之间创建循环引用(例如,对象 A 引用对象 B,对象 B 又引用对象 A),从而导致堆栈耗尽。
  • 爆炸式解压(Zip 炸弹): FlateDecode 流从几 KB 解压缩到数 GB,耗尽系统内存。

Delphi 中的防御性解析策略

在 Delphi 中原生地解析 PDF 时,您必须进行防御性编程。您不能信任 PDF 字典中提供的元数据。

1. 打破循环引用

在递归遍历 PDF 对象树时,您必须维护已访问对象的历史记录以防止无限循环。

uses
  System.Generics.Collections, System.SysUtils;

// A safe recursive function to walk the PDF tree
procedure ParsePDFDictionary(DictObj: TPDFDictionary; Visited: TList<Integer>);
var
  ObjID: Integer;
begin
  ObjID := DictObj.ObjectID;
  
  if Visited.Contains(ObjID) then
  begin
    Writeln('Warning: Circular reference detected. Aborting branch.');
    Exit;
  end;
  
  Visited.Add(ObjID);
  
  try
    // Process child objects safely...
  finally
    // Allow siblings to traverse, but prevent vertical recursion loops
    Visited.Remove(ObjID);
  end;
end;

2. 防御 Zip 炸弹

应用 FlateDecode 过滤器对流进行解压时,您必须严格限制最大解压尺寸。切勿盲目地根据 /Length 字典键分配内存。

const
  MAX_DECOMPRESSED_SIZE = 1024 * 1024 * 50; // 50 MB safety limit

procedure DecompressPDFStream(CompressedStream, OutputTarget: TStream);
var
  ZLibStream: TZDecompressionStream;
  Buffer: array[0..8191] of Byte;
  BytesRead, TotalRead: Integer;
begin
  ZLibStream := TZDecompressionStream.Create(CompressedStream);
  try
    TotalRead := 0;
    repeat
      BytesRead := ZLibStream.Read(Buffer[0], SizeOf(Buffer));
      if BytesRead > 0 then
      begin
        TotalRead := TotalRead + BytesRead;
        if TotalRead > MAX_DECOMPRESSED_SIZE then
          raise Exception.Create('Security Exception: Decompression bomb detected!');
          
        OutputTarget.WriteBuffer(Buffer[0], BytesRead);
      end;
    until BytesRead = 0;
  finally
    ZLibStream.Free;
  end;
end;

利用加固引擎与安全组件

从头编写一个完全安全的 PDF 解析器是一项浩大的工程。行业标准的方法是使用经过深度模糊测试(fuzz-tested)的加固引擎,如 PDFium,或依赖经过严格测试的原生库。

PDFium 是 Google Chrome 使用的核心渲染引擎。由于 Chrome 每天处理数以百万计的不可信 PDF,PDFium 受到 Google Project Zero 持续而猛烈的模糊测试。它能够从容地处理格式错误的 XRef、损坏的流和循环引用。

同样,HotPDF ComponentDelphi PDF Library 等原生组件开箱即用地集成了强大的防御性解析策略。它们实现了专门针对 Delphi 和 C++Builder 环境设计的严格边界检查、递归深度限制器以及内存泄漏预防机制。

无论您是选择通过 Delphi 包装器调用 PDFium 进行渲染,还是利用 HotPDF 等原生组件生成和处理文档,您都能获得企业级的安全边界,保护您的用户和服务器免受恶意负载的攻击,而无需自己编写防御性解析器。

注意:安全、经过模糊测试的解析能力贯穿我们的整个产品套件,包括 HotPDF ComponentDelphi PDF Library 以及 PDFium Component