Technical Article

การตรวจสอบความเสี่ยงด้านความปลอดภัยของ PDF ด้วย PDFium ใน Delphi

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

บทความนี้จะนำทางไปดูขั้นตอนการตรวจสอบแบบอ่านอย่างเดียวแบบคงที่กับความเสี่ยงเหล่านั้นโดยใช้ส่วนประกอบ PDFium สำหรับ Delphi และ Lazarus การตรวจสอบนี้จะไม่วาดภาพหน้ากระดาษใดๆ มันจะวิเคราะห์โครงสร้างเอกสาร แจกแจงส่วนต่างๆ ของไฟล์ที่มีพฤติกรรม และเขียนรายงานปกติ มันเป็นความแตกต่างระหว่างการขอให้คนแปลกหน้าเทกระเป๋าของพวกเขาที่ประตูกับการเชื่อใจพวกเขาเพียงเพราะพวกเขายิ้มให้

การตรวจสอบคืออะไร และสิ่งใดที่ไม่ใช่การตรวจสอบ

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

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

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'Incoming_Invoice.pdf';
    Pdf.Active := True;          // parses structure, renders nothing
    // audit the loaded document here
  finally
    Pdf.Free;
  end;
end;

JavaScript เอกสารในแผนผังชื่อเรื่อง

สิ่งแรกที่ต้องแจกแจงคือโค้ด เอกสาร PDF สามารถเก็บ JavaScript ระดับเอกสารได้: สคริปต์ที่ไม่ได้แนบอยู่กับหน้าหรือฟิลด์ใดๆ แต่แนบอยู่กับตัวเอกสารเอง โดยจัดเก็บไว้ในแผนผัง /Names ภายใต้รายการ /JavaScript โปรแกรมอ่านที่เข้ากันได้จะรันสคริปต์เหล่านี้เมื่อเปิดไฟล์ นี่คือกรรมวิธีที่อยู่เบื้องหลังมัลแวร์ PDF จำนวนมาก เนื่องจากมันอนุญาตให้ไฟล์รันตรรกะคำสั่งได้ทันทีที่ผู้ใช้ดับเบิลคลิกเปิดไฟล์ ก่อนที่พวกเขาจะได้อ่านตัวอักษรใดๆ เลยด้วยซ้ำ

ผู้ตรวจสอบต้องการทราบข้อเท็จจริงสองประการเกี่ยวกับสคริปต์แต่ละตัว: มันมีอยู่จริงหรือไม่ และมันเก็บข้อมูลอะไรบ้าง ตัวส่วนประกอบจะแสดงจำนวนและอนุญาตให้คุณอ่านการดำเนินการแต่ละตัวในรูปแบบของเรกคอร์ดที่เก็บชื่อของสคริปต์และตัวโค้ดทั้งหมด การอ่านเนื้อหาของสคริปต์มีความสำคัญ สคริปต์ที่ชื่อ Doc.0 ไม่ได้แจ้งข้อมูลใดๆ แก่คุณ แต่โค้ดของมันอาจเรียกใช้ app.launchURL หรือประกอบสตริงและส่งค่าไปยังจุดที่ไม่สมควรส่ง การดึงซอร์สโค้ดออกมาเพื่อให้ผู้ตรวจสอบสามารถอ่านได้คือจุดประสงค์ของการตั้งธงเตือนไฟล์ที่มีการรันโค้ดเมื่อเปิดเอกสาร

var
  I: Integer;
  Action: TPdfJavaScriptAction;
begin
  if Pdf.JavaScriptActionCount > 0 then
    WriteLn('WARNING: document runs ', Pdf.JavaScriptActionCount,
            ' script(s) on open');
  for I := 0 to Pdf.JavaScriptActionCount - 1 do
  begin
    Action := Pdf.JavaScriptAction[I];
    WriteLn('  script "', Action.Name, '":');
    WriteLn(Action.Script);   // full body, for a human to read
  end;
end;

ไฟล์กับตัวประมวลผลภายนอกหรือระบบที่ทำงานอยู่ ข้อมูลจินตภาพที่เป็นศูนย์จะเหลือเพียงส่วนจริงเดี่ยวๆ และส่วนจริงที่เป็นศูนย์จะตัดการแสดงผล 0+ ด้านหน้าออกไป

การดำเนินการ Launch และ URI

พฤติกรรมถัดไปที่ต้องทำบัญชีรายงานจะอยู่บนลิงก์และคำอธิบายประกอบ ประเภทการดำเนินการสองประเภทที่สำคัญที่สุดสำหรับผู้ตรวจสอบคือ การดำเนินการแบบ Launch ซึ่งจะเปิดโปรแกรมภายนอกหรือเปิดไฟล์ท้องถิ่นเมื่อลิงก์ทำงาน และการดำเนินการแบบ URI ซึ่งจะเปิดเป้าหมายเว็บ ผู้ตรวจสอบที่ดูเอกสารที่น่าสงสัยควรจะสามารถมองเห็นได้โดยไม่ต้องคลิกอะไรเลย ว่าปุ่มบนหน้าที่สามถูกเชื่อมโยงเพื่อเปิดใช้ cmd.exe หรือเปิด URL ที่ไม่ตรงกับแบรนด์บนหน้ากระดาษ

ตัวส่วนประกอบจะแบ่งประเภทลิงก์ที่พบและเปิดเผยประเภทการดำเนินการและเส้นทางเป้าหมายสำหรับแต่ละตัว เพื่อให้การตรวจสอบสามารถแสดงรายการการดำเนินการ Launch และ URI พร้อมด้วยปลายทางได้ นี่คือการรายงาน ไม่ใช่การรันทำงานจริง ผู้ตรวจสอบจะอ่านการดำเนินการจากโครงสร้างและบันทึกข้อมูลไว้ โดยไม่มีการติดตามลิงก์นั้นไปจริงๆ

การควบคุมตัวแสดงผลที่เรนเดอร์เอกสารคือจุดที่การติดตามการดำเนินการจะเกิดขึ้น และท่าทีเริ่มต้นของมันถูกตั้งค่าให้ระมัดระวังเป็นพิเศษ การควบคุม TPdfView มีชุดข้อมูล LinkOptions ที่ตัดสินว่าลิงก์ประเภทใดจะทำงานโดยอัตโนมัติเมื่อมีการคลิก ค่าเริ่มต้นคือ [loAutoGoto, loAutoOpenURI] ซึ่งหมายความว่าการกระโดดภายในเอกสารและ URL เว็บอาจเปิดทำงาน แต่ไม่มี loAutoLaunch อยู่ด้วย ดังนั้นการดำเนินการ Launch จะไม่มีทางรันทำงานโดยอัตโนมัติ สำหรับขั้นตอนการตรวจสอบ คุณสามารถไปไกลกว่านั้นได้โดยการล้างชุดข้อมูลนี้ออกทั้งหมด เพื่อไม่ให้มีสิ่งใดทำงานโดยอัตโนมัติในขณะที่คุณกำลังตัดสินใจว่าจะเชื่อถือไฟล์นี้หรือไม่

// Audit posture for the viewer: nothing auto-runs, nothing auto-opens.
View.LinkOptions := [];

// The shipped default already withholds launch:
//   default = [loAutoGoto, loAutoOpenURI]
//   loAutoLaunch is NOT in the default set, so external programs
//   are never started on a stray click out of the box.

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

ระดับสิทธิ์ MDP ของลายเซ็นดิจิทัล

ลายเซ็นเปลี่ยนมุมมองคำถาม ลายเซ็นธรรมดาจะรับรองไบต์ข้อมูล ณ เวลาที่เซ็น ลายเซ็นรับรอง (certification signature) ซึ่งเป็นชนิดที่สร้างขึ้นด้วยกฎการตรวจจับและป้องกันการแก้ไขเอกสาร จะก้าวไปอีกขั้น: มันจะประกาศว่าสิ่งใดที่สามารถเปลี่ยนแปลงได้อย่างถูกต้องตามกฎหมายหลังจากเอกสารได้รับการรับรองแล้ว และโปรแกรมอ่านที่เข้ากันได้จะแจ้งเตือนหากมีการแตะต้องสิ่งใดๆ นอกเหนือจากการอนุญาตนั้น การอ่านระดับสิทธิ์นั้นจะช่วยบอกผู้ตรวจสอบว่าไฟล์ได้รับการรับรองหรือไม่ และหากเป็นเช่นนั้น ไฟล์ถูกกำหนดให้ล็อกไว้แน่นหนาเพียงใด

สิทธิ์อนุญาต MDP คือจำนวนเต็มที่มีสามค่าที่กำหนด ค่าระดับ 1 หมายถึงไม่อนุญาตให้มีการเปลี่ยนแปลงใดๆ เลย การแก้ไขใดๆ จะทำให้การรับรองล้มเหลว ค่าระดับ 2 อนุญาตให้กรอกฟอร์มและลงลายมือชื่อ ซึ่งเป็นกรณีทั่วไปสำหรับสัญญาที่กำหนดให้กรอกและเซ็นชื่อแต่ไม่มีการแก้ไขอย่างอื่น ค่าระดับ 3 จะอนุญาตเพิ่มเติมในเรื่องคำอธิบายประกอบควบคู่ไปกับการกรอกฟอร์มและลงลายมือชื่อ การทราบระดับสิทธิ์นี้จะช่วยให้ตรรกะการรับข้อมูลของคุณเข้าใจเจตนา: เอกสารที่ได้รับการรับรองระดับ 1 แต่มีฟิลด์ฟอร์มหรือสคริปต์อยู่ด้วยนั้นถือเป็นเรื่องขัดแย้งในตัวเอง และความขัดแย้งนั้นสมควรได้รับการแจ้งเตือน

ตัวส่วนประกอบจะอ่านจำนวนของลายเซ็นและเปิดเผยแต่ละลายเซ็นในรูปของเรกคอร์ดที่ฟิลด์ Permission เก็บค่า MDP นั้น ซึ่งได้ข้อมูลโดยตรงจากฟังก์ชันเรียกใช้งาน FPDFSignatureObj_GetDocMDPPermission ภายใน สิทธิ์ที่เป็นศูนย์หมายความว่าลายเซ็นไม่ใช่ลายเซ็นรับรอง (DocMDP) จึงไม่มีข้อมูลการล็อกระดับเอกสารรายงาน

var
  I: Integer;
  Sig: TPdfSignature;
begin
  if Pdf.SignatureCount = 0 then
    WriteLn('document is not signed')
  else
    for I := 0 to Pdf.SignatureCount - 1 do
    begin
      Sig := Pdf.Signature[I];
      case Sig.Permission of
        1: WriteLn('certified: no changes allowed');
        2: WriteLn('certified: form fill and signing allowed');
        3: WriteLn('certified: form fill, signing and annotations allowed');
      else
        WriteLn('signed, but not a DocMDP certification');
      end;
    end;
end;

ส่วนอื่นๆ ของขอบเขตความเสี่ยง: ไฟล์ที่ฝังตัวและ XFA

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

การตรวจหา XFA คือแฟล็กตัวตรวจสอบอีกตัว ฟอร์มแบบ XFA จะเข้ามาแทนที่ AcroForm แบบคงที่ด้วยสถาปัตยกรรมฟอร์มบนพื้นฐาน XML ซึ่งมาพร้อมกับโมเดลการเรนเดอร์และสคริปต์ของตัวเอง ซึ่งมีขอบเขตที่ใหญ่และซับซ้อนกว่าฟอร์มธรรมดา คุณไม่จำเป็นต้องประมวลผล XFA เพื่อระบุว่ามีมันอยู่ การมีอยู่ของมันคือสัญญาณระบุว่าไฟล์มีส่วนการโต้ตอบที่ซับซ้อนซึ่งควรค่าแก่การตรวจสอบอย่างใกล้ชิด ตัวส่วนประกอบจะรายงานค่านี้เป็นบูลีนเดี่ยว

var
  I: Integer;
begin
  if Pdf.XFA then
    WriteLn('NOTE: document contains an XFA form layer');

  if Pdf.AttachmentCount > 0 then
  begin
    WriteLn('embedded files: ', Pdf.AttachmentCount);
    for I := 0 to Pdf.AttachmentCount - 1 do
      WriteLn('  - ', Pdf.AttachmentName[I]);
  end;
end;

หนึ่งรูทีนแบบอ่านอย่างเดียวที่ทำหน้าที่เขียนรายงาน

เมื่อรวมส่วนประกอบต่างๆ เข้าด้วยกัน การตรวจสอบคือกระบวนการเดียวที่โหลดเอกสาร แจกแจงสคริปต์และเนื้อหาของสคริปต์ แสดงรายการเป้าหมาย Launch และ URI รายงานระดับสิทธิ์ MDP ของลายเซ็น บันทึกไฟล์แนบและ XFA และเขียนผลการตรวจสอบลงในล็อกไฟล์ มันไม่ได้เรนเดอร์ภาพใดๆ จึงมีต้นทุนการประมวลผลต่ำและไม่ถูกหลอกลวงให้แสดงเนื้อหาที่เป็นอันตราย เอาต์พุตคือระเบียนข้อความปกติที่ผู้ตรวจสอบหรือกฎปลายทางสามารถนำไปดำเนินการได้

รูปแบบการทำงานที่ดีในทางปฏิบัติคือการรวบรวมผลลัพธ์แต่ละตัวเป็นข้อความแต่ละบรรทัด โดยใส่คำนำหน้าสำหรับรายการที่มีความเสี่ยงเพื่อให้เรียงไว้ด้านบนสุดของคิวการรีวิว และบันทึกข้อมูลทั้งหมดไว้ข้างๆ ไฟล์เอกสาร เอกสารที่ไม่มีสคริปต์ ไม่มีการดำเนินการ Launch ไม่มีไฟล์แนบ ไม่มี XFA และไม่มีลายเซ็นหรือมีการรับรองที่สอดคล้องกันจะผ่านการตรวจสอบอย่างเงียบๆ ส่วนเอกสารที่ส่งสัญญาณแจ้งเตือนหลายแฟล็กพร้อมกันคือเอกสารที่บุคคลควรตรวจสอบก่อนที่ขั้นตอนอื่นจะเปิดมันขึ้นมา การตรวจสอบไม่ได้ตัดสินใจเรื่องความน่าเชื่อถือแทนคุณ แต่มันช่วยให้การตัดสินใจนั้นมีข้อมูลรองรับแทนที่จะเดาสุ่ม

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