Dokumenty PDF oferują ogromne możliwości, a ta elastyczność niesie ze sobą nieodłączne ryzyko bezpieczeństwa. Ponieważ PDF obsługuje osadzone pliki, interaktywny JavaScript i złożone strumienie binarne, jest często wykorzystywany jako wektor dystrybucji złośliwego oprogramowania. Przepełnienia bufora, odczyty spoza zakresu pamięci i przepełnienia liczb całkowitych w źle napisanych parserach PDF mogą prowadzić do zdalnego wykonania kodu (RCE)
Jeśli tworzysz w Delphi aplikację przyjmującą pliki PDF przesyłane przez użytkowników (np. portal do przyjmowania dokumentów), bezpieczne dla pamięci parsowanie PDF jest krytycznym wymaganiem bezpieczeństwa
Typowe wektory ataków w plikach PDF
Złośliwe pliki PDF zazwyczaj atakują luki w samym parserze, a nie w systemie operacyjnym. Typowe techniki to:
- Zniekształcone tabele krzyżowych odniesień (XRef): Tworzenie przesunięć wskaźników wykraczających poza zakres, powodujące awarię parsera lub ujawnienie zawartości pamięci
- Nieskończone pętle: Tworzenie cyklicznych odniesień między obiektami PDF (np. obiekt A odwołuje się do obiektu B, który odwołuje się do obiektu A), prowadzące do wyczerpania stosu
- Eksplodująca dekompresja (Zip Bomb): Strumienie FlateDecode, które rozpakowują się z kilku kilobajtów do gigabajtów, wyczerpując pamięć systemową
Strategie defensywnego parsowania w Delphi
Parsując pliki PDF natywnie w Delphi, musisz programować defensywnie. Nie możesz ufać metadanym zawartym w słownikach PDF
1. Wykrywanie cyklicznych odniesień
Podczas rekurencyjnego przechodzenia przez drzewo obiektów PDF musisz prowadzić historię odwiedzonych obiektów, aby zapobiec nieskończonym pętlom
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 Zip Bomb
Stosując filtr FlateDecode do dekompresji strumienia, musisz bezwzględnie ograniczyć maksymalny rozmiar danych wyjściowych. Nigdy nie przydzielaj pamięci ślepo na podstawie klucza słownika /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;
Korzystanie z wzmocnionych silników i bezpiecznych komponentów
Napisanie od podstaw całkowicie bezpiecznego parsera PDF to ogromne przedsięwzięcie. Branżowym standardem jest korzystanie z wzmocnionego, intensywnie testowanego za pomocą fuzz testów silnika, takiego jak PDFium, lub poleganie na rygorystycznie przetestowanych bibliotekach natywnych
PDFium to podstawowy silnik renderowania używany przez Google Chrome. Ponieważ Chrome przetwarza miliony niezaufanych plików PDF każdego dnia, PDFium jest poddawany agresywnemu, ciągłemu fuzz testingowi przez OSS-Fuzz i ClusterFuzz. Obsługuje zniekształcone tabele XRef, uszkodzone strumienie i odwołania cykliczne bez awarii
Podobnie natywne komponenty takie jak HotPDF Component i Delphi PDF Library zawierają dobrze przetestowane strategie defensywnego parsowania od razu po wyjęciu z pudełka. 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ę korzystać z PDFium za pośrednictwem wrappera Delphi do renderowania, czy używać natywnych komponentów, takich jak HotPDF, do generowania i przetwarzania dokumentów - zyskujesz korporacyjny poziom bezpieczeństwa, który chroni Twoich użytkowników i serwery przed złośliwymi ładunkami bez konieczności samodzielnego pisania defensywnych parserów
Uwaga: Bezpieczne, przetestowane za pomocą fuzz testów możliwości parsowania są dostępne w całym naszym pakiecie, w tym w HotPDF Component, Delphi PDF Library i PDFium Component