מסמכי PDF הם עוצמתיים להפליא, אך עוצמה זו מלווה בסיכוני אבטחה מובנים. מכיוון שקובצי PDF תומכים בקבצים מוטמעים, ב-JavaScript אינטראקטיבי ובזרמים בינאריים מורכבים, הם משמשים לעתים קרובות כווקטורים להעברת תוכנות זדוניות (malware). גלישות חוצץ (buffer overflows), קריאות מחוץ לגבולות (out-of-bounds reads) וגלישות מספרים שלמים (integer overflows) במנתחי PDF כתובים בצורה גרועה עלולים להוביל להרצת קוד מרחוק (RCE).
אם אתם בונים יישום ב-Delphi המקבל קובצי PDF המועלים על ידי משתמשים (למשל, פורטל לקליטת מסמכים), הבטחת ניתוח PDF בטוח בזיכרון (memory-safe PDF parsing) היא דרישת אבטחה קריטית.
וקטורי תקיפה נפוצים ב-PDF
מסמכי PDF זדוניים בדרך כלל מכוונים לפגיעויות במנתח עצמו, ולא במערכת ההפעלה. טכניקות נפוצות כוללות:
- טבלאות XRef (Cross-Reference) פגומות: יצירת היסטים (offsets) של מצביעים שמובילים מחוץ לגבולות, מה שגורם לקריסת המנתח או מאפשר חשיפת זיכרון.
- לולאות אינסופיות: יצירת הפניות מעגליות בין אובייקטים של PDF (למשל, אובייקט A מפנה לאובייקט B, אשר מפנה חזרה לאובייקט A) המובילות למיצוי המחסנית (stack exhaustion).
- פיצוץ פריסה (Zip Bombs): זרמי 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 Bombs
בעת הפעלת מסנן ה-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 מאובטח לחלוטין מאפס היא משימה כבירה. הגישה הסטנדרטית בתעשייה היא להשתמש במנוע מוקשח שנבדק בקפדנות באמצעות fuzzing כמו PDFium, או להסתמך על ספריות מקומיות (native) שנבדקו בקפידה.
PDFium הוא מנוע הרינדור המרכזי המשמש את Google Chrome. מכיוון ש-Chrome מעבד מדי יום מיליוני קובצי PDF שאינם מהימנים, PDFium נתון ל-fuzzing אגרסיבי ורציף על ידי Project Zero של Google. הוא מטפל ב-XRefs פגומים, זרמים שבורים והפניות מעגליות בצורה חלקה.
באופן דומה, רכיבים מקומיים כמו ה-HotPDF Component וה-Delphi PDF Library משלבים אסטרטגיות ניתוח הגנתיות חזקות היישר מהקופסה. הם מיישמים בדיקת גבולות קפדנית, מגבילי עומק רקורסיביים ומנגנוני מניעת דליפת זיכרון המיועדים במיוחד לסביבות Delphi ו-C++Builder.
בין אם תבחרו לצרוך את PDFium דרך מעטפת (wrapper) של Delphi עבור רינדור, ובין אם תשתמשו ברכיבים מקומיים כמו HotPDF ליצירה ועיבוד של מסמכים, אתם יורשים מעטפת אבטחה ברמה ארגונית, המגנה על המשתמשים והשרתים שלכם מפני מטענים זדוניים (payloads) מבלי שתצטרכו לכתוב מנתחים הגנתיים בעצמכם.
הערה: יכולות ניתוח מאובטחות שנבדקו באמצעות fuzzing זמינות בכל חבילת המוצרים שלנו, לרבות HotPDF Component, Delphi PDF Library, ו-ה-PDFium Component.