Technical Article

Безопасен за паметта синтактичен анализ на PDF: Защита срещу злонамерени документи

PDF документите са невероятно мощни, но тази мощност идва с присъщи рискове за сигурността. Тъй като PDF файловете поддържат вградени файлове, интерактивен JavaScript и сложни бинарни потоци, те често се използват като вектори за доставяне на зловреден софтуер. Препълването на буфера (buffer overflows), четенето извън границите (out-of-bounds reads) и целочисленото препълване (integer overflows) в лошо написани PDF парсери могат да доведат до отдалечено изпълнение на код (RCE).

Ако изграждате приложение в Delphi, което приема качени от потребителите PDF файлове (напр. портал за приемане на документи), осигуряването на безопасен за паметта синтактичен анализ на PDF е критично изискване за сигурност.

Често срещани вектори за атака при PDF

Злонамерените PDF файлове обикновено се насочват към уязвимости в самия парсер, а не в операционната система. Често срещаните техники включват:

  • Деформирани таблици с кръстосани препратки (XRef): Създаване на отмествания на указатели, които водят извън границите, причинявайки срив на парсера или позволявайки разкриване на памет.
  • Безкрайни цикли: Създаване на кръгови препратки между PDF обекти (напр. Обект А препраща към Обект Б, който препраща към Обект А), което води до изчерпване на стека.
  • Експлодираща декомпресия (Zip бомби): FlateDecode потоци, които се декомпресират от няколко килобайта до гигабайти, изчерпвайки системната памет.

Стратегии за защитен синтактичен анализ в Delphi

Когато анализирате PDF файлове нативно в Delphi, трябва да програмирате защитно. Не можете да се доверите на метаданните, предоставени в 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 е подложен на агресивен, непрекъснат фъзинг от страна на Project Zero на Google. Той се справя елегантно с деформирани XRef-ове, счупени потоци и циклични препратки.

По подобен начин, нейтив компоненти като HotPDF Component и Delphi PDF Library включват стабилни дефанзивни стратегии за парсване по подразбиране. Те имплементират строга проверка на границите, ограничители на рекурсивната дълбочина и механизми за предотвратяване на изтичане на памет, проектирани специално за среди на Delphi и C++Builder.

Независимо дали изберете да консумирате PDFium чрез Delphi wrapper за рендъринг, или да използвате нейтив компоненти като HotPDF за генериране и обработка на документи, вие наследявате периметър за сигурност на корпоративно ниво, защитаващ вашите потребители и вашите сървъри от злонамерени полезни товари, без да се налага сами да пишете дефанзивни парсери.

Забележка: Сигурни, тествани с фъзинг възможности за парсване са налични в целия ни пакет, включително HotPDF Component, Delphi PDF Library и PDFium Component.