เอกสาร 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 การแสดงผล ข้อความ ฟอร์ม และลายเซ็นที่มีอธิบายในส่วนอื่นของบล็อกนี้