Dokumenty PDF są niesamowicie potężne, ale ta wszechstronność niesie ze sobą nieodłączne ryzyko bezpieczeństwa. Ponieważ format PDF obsługuje pliki osadzone, interaktywny język JavaScript oraz złożone strumienie binarne, jest on często wykorzystywany jako wektor dystrybucji złośliwego oprogramowania. Przepełnienia bufora, odczyty poza zakresem pamięci (out-of-bounds) oraz przepełnienia liczb całkowitych w źle napisanych parserach PDF mogą prowadzić do zdalnego wykonania kodu (RCE).
Jeśli tworzysz w Delphi aplikację, która akceptuje przesłane przez użytkowników pliki PDF (np. portal do przyjmowania dokumentów), zapewnienie bezpiecznego dla pamięci parsowania plików PDF jest krytycznym wymogiem bezpieczeństwa.
Powszechne wektory ataków w PDF
Złośliwe pliki PDF zazwyczaj obierają za cel luki w samym parserze, a nie w systemie operacyjnym. Popularne techniki to między innymi:
- Zniekształcone tabele odsyłaczy (XRef): Tworzenie takich przesunięć wskaźników (pointer offsets), które wykraczają poza zakres, co powoduje awarię parsera lub umożliwia ujawnienie zawartości pamięci.
- Nieskończone pętle: Tworzenie odniesień cyklicznych między obiektami PDF (np. Obiekt A odwołuje się do Obiektu B, który z kolei odwołuje się do Obiektu A), co prowadzi do wyczerpania stosu.
- Eksplodująca dekompresja (Zip Bombs): Strumienie FlateDecode, które z kilku kilobajtów dekompresują się do gigabajtów, wyczerpując pamięć systemu.
Strategie defensywnego parsowania w Delphi
Podczas natywnego parsowania plików PDF w Delphi musisz programować defensywnie. Nie możesz ufać metadanym zawartym w słownikach PDF.
1. Rozwiązywanie cyklicznych odniesień
Kiedy rekurencyjnie przechodzisz przez drzewo obiektów PDF, musisz zachować historię odwiedzonych już obiektów, by zapobiec pętlom nieskończonym.
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. Ochrona przed bombami dekompresyjnymi (Zip Bombs)
Podczas korzystania z filtra FlateDecode do dekompresji strumienia, musisz bezwzględnie ograniczyć jego maksymalny dopuszczalny rozmiar rozszerzenia. Nigdy nie przydzielaj pamięci w ciemno, opierając się wyłącznie na kluczu słownikowym /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;
Wykorzystanie wzmocnionych silników i bezpiecznych komponentów
Napisanie całkowicie bezpiecznego parsera PDF od podstaw to monumentalne zadanie. Standardowym podejściem w branży jest użycie wzmocnionego, intensywnie testowanego za pomocą fuzzingu silnika, takiego jak PDFium, lub poleganie na rygorystycznie przetestowanych natywnych bibliotekach.
PDFium to główny silnik renderujący używany przez Google Chrome. Ponieważ Chrome przetwarza codziennie miliony niezaufanych plików PDF, PDFium jest poddawane agresywnemu, ciągłemu fuzzingowi przez Project Zero firmy Google. Płynnie obsługuje zniekształcone tabele XRef, uszkodzone strumienie i odniesienia cykliczne.
Podobnie, natywne komponenty, takie jak HotPDF Component i Delphi PDF Library, mają wbudowane solidne, defensywne strategie parsowania. Implementują one rygorystyczne sprawdzanie granic, ograniczniki głębokości rekurencji i mechanizmy zapobiegania wyciekom pamięci zaprojektowane specjalnie dla środowisk Delphi i C++Builder.
Niezależnie od tego, czy zdecydujesz się na korzystanie z PDFium za pośrednictwem wrappera Delphi do renderowania, czy na wykorzystanie natywnych komponentów, takich jak HotPDF, do generowania i przetwarzania dokumentów, dziedziczysz obwód bezpieczeństwa klasy korporacyjnej, chroniący użytkowników i serwery przed złośliwymi ładunkami, bez konieczności samodzielnego pisania defensywnych parserów.
Uwaga: Bezpieczne, testowane za pomocą fuzzingu możliwości parsowania są dostępne w całym naszym pakiecie, obejmującym HotPDF Component, Delphi PDF Library i PDFium Component.