技術文章

Delphi 中記憶體安全的 PDF 解析:防禦惡意檔案

PDF 檔案可以做很多事情,而這種彈性也伴隨著固有的安全風險。因為 PDF 支援內嵌檔案、互動式 JavaScript 以及複雜的二進位串流,它們經常被用作傳遞惡意軟體的媒介。編寫不良的 PDF 解析器中的緩衝區溢位、越界讀取和整數溢位,都可能導致遠端程式碼執行(RCE)

如果您正在建置一個接受使用者上傳 PDF(例如檔案攝取入口網站)的 Delphi 應用程式,確保記憶體安全的 PDF 解析是一項至關重要的安全需求

常見的 PDF 攻擊媒介

惡意 PDF 通常會針對解析器本身的漏洞,而非作業系統。常見的技術包含:

  • 格式錯誤的交互參照(XRef)表: 製作會導致越界指標偏移,使解析器崩潰或允許記憶體洩漏
  • 無窮迴圈: 在 PDF 物件之間建立循環參照(例如,物件 A 參照物件 B,而物件 B 又參照物件 A),導致堆疊耗盡
  • 爆炸性解壓縮(Zip 炸彈): FlateDecode 串流從幾 KB 解壓縮為數 GB,耗盡系統記憶體

Delphi 中的防禦性解析策略

在 Delphi 中原生解析 PDF 時,您必須採取防禦性的程式設計。您不能信任 PDF 字典中提供的詮釋資料(metadata)

1. 打破循環參照

在遞迴走訪 PDF 物件樹時,您必須維護已走訪物件的歷史記錄,以防止無窮迴圈

uses
  System.Generics.Collections, System.SysUtils;

// 一個安全走訪 PDF 樹的遞迴函式
procedure ParsePDFDictionary(DictObj: TPDFDictionary; Visited: TList<Integer>);
var
  ObjID: Integer;
begin
  ObjID := DictObj.ObjectID;
  
  if Visited.Contains(ObjID) then
  begin
    Writeln('警告:偵測到循環參照。中止分支。');
    Exit;
  end;
  
  Visited.Add(ObjID);
  
  try
    // 安全地處理子物件...
  finally
    // 允許同層物件走訪,但防止垂直的遞迴迴圈
    Visited.Remove(ObjID);
  end;
end;

2. 防禦 Zip 炸彈

當套用 FlateDecode 篩選器來解壓縮串流時,您必須嚴格限制最大擴張大小。絕不要盲目地根據 /Length 字典鍵來配置記憶體

const
  MAX_DECOMPRESSED_SIZE = 1024 * 1024 * 50; // 50 MB 安全限制

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('安全性例外:偵測到解壓縮炸彈!');
          
        OutputTarget.WriteBuffer(Buffer[0], BytesRead);
      end;
    until BytesRead = 0;
  finally
    ZLibStream.Free;
  end;
end;

善用強化的引擎與安全的元件

從頭開始撰寫一個完全安全的 PDF 解析器是一項艱鉅的任務。業界標準的作法是使用一個經過強化且經過大量模糊測試(fuzz-tested)的引擎,如 PDFium,或者是依賴經過嚴格測試的原生程式庫

PDFium 是 Google Chrome 使用的核心渲染引擎。因為 Chrome 每天處理數以百萬計不受信任的 PDF,PDFium 受到 OSS-Fuzz 與 ClusterFuzz 持續且猛烈的模糊測試。它可以優雅地處理格式錯誤的 XRef、損壞的串流以及循環參照

同理,原生的元件如 HotPDF 元件Delphi PDF 庫 開箱即結合了經過充分測試的防禦性解析策略。它們實作了嚴格的邊界檢查、遞迴深度限制器以及記憶體洩漏預防機制,這些都是專為 Delphi 與 C++Builder 環境所設計的

無論您選擇透過 Delphi 封裝程式使用 PDFium 進行渲染,或是利用如 HotPDF 等原生元件進行檔案產生與處理,您都繼承了企業級的安全邊界,可以保護您的使用者和伺服器免於惡意酬載的攻擊,而不需要您自己編寫防禦性的解析器

注意:安全且經過模糊測試的解析功能在我們的整個套件中都可使用,包含 HotPDF 元件Delphi PDF 庫 以及 PDFium 元件