ملف PDF ليس مجرد ورقة. إنه حاوية يمكنها حمل نصوص برمجية تعمل عند فتح الملف، وروابط تشغل برامج خارجية، وروابط تتصل بخوادم الويب، وملفات متداخلة داخل ملفات، وتوقيع يدعي أن المستند لم يتغير منذ أن كفله شخص ما. عندما يصل ملف من مصدر لا تتحكم فيه، فإن الخطوة الأولى الأكثر أماناً ليست عرضه. بل هي قراءة ما يقوله الملف عن نفسه وبناء جرد لكل شيء يمكن أن يحاول القيام به، بحيث يمكن لشخص اتخاذ قرار بشأن ما إذا كان ينتمي إلى سير عملك على الإطلاق.
يستعرض هذا المقال عملية تدقيق ثابتة للقراءة فقط على سطح المخاطر هذا باستخدام مكون PDFium لـ Delphi وLazarus. التدقيق لا يرسم صفحة أبداً. فهو يحلل بنية المستند، ويسرد أجزاء الملف التي تحمل سلوكاً، ويكتب تقريراً بسيطاً. إنه الفرق بين مطالبة شخص غريب بإفراغ جيوبه عند الباب والثقة به لأنه ابتسم.
ما هو التدقيق، وما ليس هو
كن واضحاً بشأن الحدود. تعرض المعاينة المعزولة (sandboxed preview) ملفاً تحت قيود مشددة بحيث يمكن للمستخدم النظر إليه دون أن يلمس الملف بقية الجهاز. التدقيق يأتي قبل ذلك. إنه فحص خالٍ من العرض والمخرجات الوحيدة له هي وصف لسطح التهديد: أي النصوص البرمجية موجودة، وأي الإجراءات مرتبطة بالروابط، وما إذا كان الملف موقعاً ومدى إحكام ذلك، وما هو مرفق به. تقوم بتشغيله عندما يعبر مستند ما حدود الثقة، عند استلامه من البريد الإلكتروني، أو نموذج تحميل، أو تغذية شريك، قبل أن تفتحه أي مرحلة لاحقة بشكل حقيقي.
يقوم المكون بتحميل المستند بنفس الطريقة للتدقيق كما لأي شيء آخر. تضبط اسم الملف وتنشطه، مما يحلل بيانات الإسناد الترافقي وكتالوج المستند دون عرض صفحة واحدة. كل ما يلي يقرأ من تلك الحالة المحملة غير المعروضة.
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;
الملف الذي لا يحتوي على نصوص مستندات ليس آمناً تلقائياً، لأن نصوص الصفحات والحقول موجودة أيضاً، ولكن الملف الذي يحتوي على نصوص مستندات يستحق دائماً نظرة ثانية. عدد الحضور وحده هو بوابة مفيدة، والجسم هو ما يحول البوابة إلى حكم.
إجراءات التشغيل وإجراءات URI
السلوك التالي الذي يجب جرده يعيش على الروابط والتعليقات التوضيحية. هناك نوعان من الإجراءات يهمان المدقق أكثر من غيرهما. يقوم إجراء التشغيل (Launch) ببدء تشغيل برنامج خارجي أو فتح ملف محلي عند تنشيط الرابط. ويفتح إجراء URI هدفاً على الويب. يجب أن يكون المراجع الذي ينظر إلى مستند مشبوه قادراً على رؤية، دون النقر فوق أي شيء، أن زراً في الصفحة الثالثة متصل لتشغيل cmd.exe أو لفتح عنوان URL لا يطابق العلامة التجارية على الصفحة.
يصنف المكون الروابط التي يعثر عليها ويكشف عن نوع الإجراء ومسار الهدف لكل منها، بحيث يمكن للتدقيق سرد كل إجراء تشغيل وإجراء URI مع وجهته. هذا إعداد تقارير، وليس تنفيذاً. يقرأ المدقق الإجراء من البنية ويكتبه. ولا يتبعه أبداً.
عنصر التحكم في العارض الذي يعرض المستندات هو المكان الذي سيحدث فيه اتباع الإجراء، وموقفه الافتراضي حذر عمداً. يحتوي عنصر التحكم TPdfView على مجموعة LinkOptions تحدد أنواع الروابط التي تعمل تلقائياً عند النقر. والخيار الافتراضي هو [loAutoGoto, loAutoOpenURI]، مما يعني أن قفزات داخل المستند وعناوين URL للويب قد تفتح، ولكن loAutoLaunch غائب، لذا لا تعمل إجراءات التشغيل تلقائياً أبداً. بالنسبة لسير عمل التدقيق، يمكنك الذهاب إلى أبعد من ذلك ومسح المجموعة بالكامل، بحيث لا يعمل شيء تلقائياً على الإطلاق بينما لا تزال تقرر ما إذا كنت ستثق في الملف.
// 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 للتوقيع الرقمي
تغير التواقيع السؤال. يشهد التوقيع البسيط على البايتات في وقت التوقيع. ويذهب توقيع الاعتماد، وهو النوع الذي يتم إنشاؤه بقاعدة كشف ومنع تعديل المستند، إلى أبعد من ذلك: فهو يعلن ما يمكن أن يتغير بشكل مشروع بعد اعتماد المستند، ويحذر برنامج العرض المتوافق إذا تم لمس أي شيء خارج هذا المسموح به. تخبر قراءة مستوى الإذن هذا المدقق ما إذا كان الملف معتمداً، وإذا كان الأمر كذلك، فما مدى القفل المقصود له.
إذن 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
يكمل عنصران إضافيان الجرد الكامل. الملفات المضمنة هي مستندات كاملة محمولة داخل ملف 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;
روتين واحد للقراءة فقط يكتب تقريراً
تجميع الأجزاء معاً يجعل التدقيق إجراءً واحداً يقوم بتحميل المستند، ويسرد نصوصه البرمجية وأجسامها، ويسرد أهداف التشغيل وURI، ويبلغ عن مستوى إذن MDP للتوقيع، ويلاحظ المرفقات وXFA، ويكتب النتائج في سجل. وهو لا يعرض شيئاً، لذا فهو رخيص التكلفة ولا يمكن خداعه لعرض محتوى صفحة معادية. والمخرجات هي سجل مسطح قابل للقراءة البشرية يمكن للمراجع أو قاعدة تالية التصرف بناءً عليه.
الشكل الذي يعمل بشكل جيد عملياً هو جمع كل نتيجة كسطر، ووضع بادئة للنتائج الخطيرة حقاً بحيث يتم فرزها في الجزء العلوي من قائمة انتظار المراجعة، وحفظ الشيء بأكمله بجانب الملف. يمر المستند الذي لا يحتوي على نصوص برمجية، ولا إجراءات تشغيل، ولا مرفقات، ولا XFA، وبدون توقيع أو باعتماد متسق، بهدوء. والمستند الذي يطلق عدة علامات في وقت واحد هو الذي يجب على الشخص رؤيته قبل أن تفتحه أي مرحلة لاحقة. التدقيق لا يتخذ قرار الثقة بالنيابة عنك. بل يتأكد من أن القرار مبني على معرفة وليس أعمى.
بمجرد أن يتجاوز الملف التدقيق وتكون بحاجة لمشاهدته، افعل ذلك تحت القيود بدلاً من برنامج العرض الافتراضي. يوضح النهج المتبع في دليلنا لبناء معاينة PDF آمنة في Delphi كيفية منع معالجة الروابط التلقائية والمحتوى النشط من العمل أثناء المشاهدة الخاضعة للرقابة. لطي هذا التعداد في خط معالجة استلام كامل مع أدوات مراجع، راجع مقال workbench لاستلام ومراجعة PDF. يعتمد كلاهما على نفس الأساس المخصص للقراءة فقط والخالي من العرض ويشحنان كجزء من مكون PDFium لـ Delphi وC++Builder، إلى جانب واجهات برمجة تطبيقات العرض والنصوص والنماذج والتوقيع المغطاة في مكان آخر من هذه المدونة.