Technical Article

การแยกวิเคราะห์ PDF ที่ปลอดภัยต่อหน่วยความจำ: การป้องกันเอกสารที่เป็นอันตราย

เอกสาร PDF ทรงพลังอย่างเหลือเชื่อ แต่ความทรงพลังนั้นมาพร้อมกับความเสี่ยงด้านความปลอดภัยที่มีอยู่โดยธรรมชาติ เนื่องจาก PDF รองรับไฟล์ที่ฝังไว้, JavaScript แบบโต้ตอบ และสตรีมไบนารีที่ซับซ้อน จึงมักถูกนำมาใช้เป็นพาหะสำหรับการส่งมัลแวร์ การเกิด Buffer overflows (หน่วยความจำล้น), Out-of-bounds reads (การอ่านข้อมูลนอกขอบเขต) และ Integer overflows (จำนวนเต็มล้น) ในตัวแยกวิเคราะห์ PDF ที่เขียนมาไม่ดี อาจนำไปสู่การเรียกใช้โค้ดจากระยะไกล (Remote Code Execution: RCE)

หากคุณกำลังสร้างแอปพลิเคชันใน Delphi ที่ยอมรับไฟล์ PDF ที่ผู้ใช้รับเข้ามา (เช่น พอร์ทัลการนำเข้าเอกสาร) การรับรองการแยกวิเคราะห์ PDF ที่ปลอดภัยต่อหน่วยความจำถือเป็นข้อกำหนดด้านความปลอดภัยที่สำคัญ

รูปแบบการโจมตี PDF ที่พบบ่อย

ไฟล์ PDF ที่เป็นอันตรายมักกำหนดเป้าหมายไปที่ช่องโหว่ในตัวแยกวิเคราะห์เองมากกว่าระบบปฏิบัติการ เทคนิคที่พบบ่อยได้แก่:

  • ตาราง Cross-Reference (XRef) ที่ผิดรูป: การสร้างออฟเซ็ตของตัวชี้ (pointer offsets) ที่นำออกนอกขอบเขต ทำให้ตัวแยกวิเคราะห์หยุดทำงานหรือยอมให้เปิดเผยหน่วยความจำ
  • ลูปอนันต์ (Infinite Loops): การสร้างการอ้างอิงแบบวงกลมระหว่างออบเจกต์ PDF (เช่น ออบเจกต์ A อ้างอิงถึงออบเจกต์ B ซึ่งอ้างอิงถึงออบเจกต์ A) นำไปสู่การใช้ทรัพยากรหน่วยความจำซ้อน (stack) จนหมด
  • การระเบิดการขยายข้อมูล (Zip Bombs): สตรีม FlateDecode ที่ขยายขนาดจากไม่กี่กิโลไบต์เป็นกิกะไบต์ ทำให้หน่วยความจำระบบหมดไป

กลยุทธ์การแยกวิเคราะห์เชิงรับใน Delphi

เมื่อทำการแยกวิเคราะห์ PDF แบบเนทีฟใน Delphi คุณต้องเขียนโปรแกรมในเชิงป้องกัน คุณไม่สามารถเชื่อถือข้อมูลเมตาที่ให้ไว้ในพจนานุกรม PDF ได้

1. การทำลายการอ้างอิงแบบวงกลม

เมื่อเดินแบบวนซ้ำ (recursively) ในต้นไม้ออบเจกต์ 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 หรือพึ่งพาไลบรารีเนทีฟที่ผ่านการทดสอบอย่างเข้มงวด

PDFium เป็นเอนจินการเรนเดอร์หลักที่ใช้โดย Google Chrome เนื่องจาก Chrome ประมวลผล PDF ที่ไม่น่าเชื่อถือหลายล้านไฟล์ทุกวัน PDFium จึงต้องเผชิญกับการทำ fuzzing อย่างต่อเนื่องและดุดันโดย Project Zero ของ Google ซึ่งสามารถจัดการกับ XRef ที่ผิดรูปแบบ สตรีมที่เสีย และการอ้างอิงแบบวนซ้ำได้อย่างราบรื่น

ในทำนองเดียวกัน คอมโพเนนต์เนทีฟเช่น HotPDF Component และ Delphi PDF Library ได้รวมเอากลยุทธ์การแยกวิเคราะห์เชิงป้องกันที่แข็งแกร่งมาให้พร้อมใช้งาน พวกเขาใช้การตรวจสอบขอบเขตที่เข้มงวด ตัวจำกัดความลึกแบบเรียกซ้ำ และกลไกการป้องกันหน่วยความจำรั่วไหลที่ออกแบบมาโดยเฉพาะสำหรับสภาพแวดล้อม Delphi และ C++Builder

ไม่ว่าคุณจะเลือกใช้ PDFium ผ่าน Delphi wrapper สำหรับการเรนเดอร์ หรือใช้คอมโพเนนต์เนทีฟเช่น HotPDF สำหรับการสร้างและประมวลผลเอกสาร คุณจะได้รับขอบเขตความปลอดภัยระดับองค์กร ปกป้องผู้ใช้และเซิร์ฟเวอร์ของคุณจากเพย์โหลดที่เป็นอันตรายโดยไม่ต้องเขียนตัวแยกวิเคราะห์เชิงป้องกันด้วยตัวเอง

หมายเหตุ: ความสามารถในการแยกวิเคราะห์ที่ปลอดภัยและผ่านการทดสอบ fuzzing มีอยู่ในชุดผลิตภัณฑ์ทั้งหมดของเรา รวมถึง HotPDF Component, Delphi PDF Library และ PDFium Component.