เอกสาร 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.