تخطَّ إلى المحتوى
getnextpdf.com

تطهير ملفات PDF غير الموثوقة: نزع المحتوى وإعادة بنائه

Spec: ISO 32000-2, §12.6.4

ملف ⁨PDF⁩ يصل من العالم الخارجي شيفرة بقدر ما هو مستند. يمكنه حمل ⁨JavaScript⁩، وإجراء ⁨Launch⁩، وملفاً تنفيذياً مُضمَّناً، وبنيةً مصنوعةً لإسقاط محلِّل. يعامل نزع المحتوى وإعادة بنائه (CDR) ذلك الملف غير موثوق به، ويُبقي فقط ما هو آمن، ويعيد بناء ملف ⁨PDF⁩ نظيف من الناجين.

تشرح هذه الصفحة كيف يفعل CdrEngine في ⁨NextPDF Enterprise⁩ ذلك — والشيء الصادق الواحد الذي ينبغي لكل مزوّد CDR قوله بصوت عالٍ: الملف المُعاد بناؤه إسقاط أمني للأصل، لا نسخة وفية له.

الأجزاء الخطيرة من ملف ⁨PDF⁩ ليست غريبة. إنها مزايا قياسية. يعرّف المعيار فهرساً كاملاً من الإجراءات — ما يحدث حين يُفتَح مستند، وحين تُعرَض صفحة، وحين يتغير حقل — وذلك الفهرس يشمل إجراءات ⁨JavaScript⁩ و⁨Launch⁩ (Spec: ISO 32000-2, §12.6.4). العارض الذي يحترم التنسيق سيشغّلها بسرور. تلك هي موطئ قدم المهاجم: ملف صحيح تماماً وخبيث تماماً في آنٍ واحد.

الترشيح بحسب امتداد الملف لا يفعل شيئاً هنا. التهديد داخل ملف ⁨PDF⁩ حسن التكوين، فالدفاع الحقيقي الوحيد هو فتحه وفهمه وتجريد الآلية النشطة قبل أن تبلغ مُصيِّراً أبداً. هذا هو موقف التحقق من المُدخَلات الذي يصفه إرشاد OWASP لرفع الملفات والتحقق من المُدخَلات: لا تثق بالبايتات أبداً، وفضّل إعادة بناء أثرٍ معروف الصلاح على فحص أثرٍ عدائي بحثاً عن توقيعات معروفة السوء.

  • يفترض CDR أن المُدخَل عدائي ويُنتِج ملفاً جديداً بدلاً من ترقيع القديم.
  • يشغّل CdrEngine::sanitize() ست مراحل: تحليل، وتحكّم في القبول، واكتشاف تهديدات، وترشيح، وتنظيف مراجع، وإعادة بناء.
  • يعيد CdrResult يخبرك بما أُزيل، وما إذا كان المستند قد قُبِل أصلاً، و — إن لم يُقبَل — لماذا رُفِض.
  • المُخرَج إسقاط أمني. وهو صراحةً ليس نسخة إثباتية، ولا تطابق تجزئة، ولا أثرَ أرشفة. ذلك عقد، لا تحفّظ.
  • هذا لـEnterprise فقط. نواة ⁨NextPDF⁩ لا تؤدي CDR.

تذكر وثائق المحرك نفسه الخط الأحمر للتصميم في ثلاث كلمات: طبقة الإسقاط الأمني. sanitize() تحويل مُتلِف غير عكوس. مسموح له بطرح بايتات بعيداً. ما لا يُسمَح له به هو التظاهر بأن النتيجة هي المستند نفسه.

خط الأنابيب مرتَّب عمداً. تضيّق كل مرحلة الثقة قبل أن تعمل المرحلة التالية عليها.

  1. ParsePdfReader builds the object graph from the raw bytes. A parse failure is a rejection, not a best-effort guess.
  2. Admission controlResource limits are checked first — object count, page count, decoded-stream size, per-stream inflation ratio. Over-budget input is rejected, never sanitised.
  3. DetectThreatDetector walks the graph and records each dangerous feature as a DetectedThreat with its object number and type.
  4. FilterObjects are partitioned into safe and removed. Catalog-level keys are stripped in place so the document catalog itself survives.
  5. Reference scrubPointers to removed objects are cleaned so the rebuilt file has no dangling references.
  6. RebuildCdrRebuilder serialises only the safe objects into a new PDF. The output is fresh structure, not an edited original.
المراحل الست لـCdrEngine::sanitize()، بالترتيب. يُحلَّل المستند، ثم يُقبَل أو يُرفَض مقابل حدود الموارد، ثم يُفحَص بحثاً عن تهديدات؛ تُرشَّح الكائنات الخطيرة، وتُنظَّف المراجع المعلَّقة، ولا تُسلسَل إلا الكائنات الآمنة الناجية في ملف PDF جديد كلياً. لا تعبر البايتات الأصلية أبداً.

القبول قبل النزع. قبل إزالة تهديد واحد، يسأل المحرك ما إذا كان المستند يستحق المعالجة أصلاً. يحمل CdrPolicy الحدود — maxObjects، وmaxPageCount، وmaxDecodedStreamBytes، وmaxInflationRatio تدافع عن قنابل فك الضغط. المستند الذي يتجاوزها يُرفَض، ويقول CdrResult ذلك بـadmitted: false وrejectionReason. هذا التمييز حامل: “نظّفناه” و”رفضناه” نتيجتان مختلفتان، ويفصلهما نوع النتيجة كي يفصلهما تقرير أخطائك أيضاً.

التهديدات مُسمّاة، لا مُخمَّنة. يربط ThreatDetector::detect() كل بنية خطيرة بـThreatTypeJavaScript، وLaunchAction، وOpenAction، وAdditionalActions، وRemoteGoTo، وSubmitForm، وImportData، وEmbeddedFiles، وRichMedia، وNamedJavaScript، وأكثر — كلٌّ مربوط بميزة ⁨PDF⁩ محددة. الكائن الذي يتعذّر تحليله إطلاقاً يصير نوع تهديده الخاص، UnparseableObject، لأن الكائن الذي يتعذّر على المُطهِّر قراءته كائن يتعذّر عليه ضمانه. كل اكتشاف DetectedThreat يحمل رقم الكائن المخالف، فتكون الإزالة دقيقة.

الإزالة تحمي المستند، لا الحمولة وحدها. بعض المفاتيح الخطيرة تستقر على فهرس المستند (الجذر) — /OpenAction ينطلق عند الفتح مثلاً. إزالة كائن الجذر بأكمله لقتل مفتاح واحد ستدمّر الفهرس وتعطّل الملف بصمت. يتولّى المحرك هذه تجريد مفاتيح في مكانها على الفهرس بدلاً من ذلك، وكحارس يفشل مغلقاً يرفض إصدار مستند مُعاد بناؤه فقد /Root خاصته أصلاً. المُطهِّر الذي يُنتِج ملفاً معطوب البنية بينما يبلّغ عن نجاح هو بالضبط نمط الإخفاق الذي وُجِد هذا الحارس لمنعه.

الشكل أدناه هو نقطة الدخول الحقيقية. تسلّم المحرك بايتات خاماً وسياسة؛ فتستعيد CdrResult صادقاً بشأن ما حدث.

<?php
declare(strict_types=1);
use NextPDF\Enterprise\Security\Cdr\CdrEngine;
use NextPDF\Enterprise\Security\Cdr\CdrPolicy;
$engine = new CdrEngine();
// Standard policy removes the known active threats — JavaScript, Launch
// actions, remote go-to, form submit/import, and the rest — while leaving
// the lossy opt-in "Strip*" cases off by default.
$result = $engine->sanitize($untrustedPdfBytes, CdrPolicy::standard());
if (!$result->admitted) {
// Rejected by admission control (e.g. object/page limit, zip bomb).
// This is NOT a sanitised document. Do not serve it; report the reason.
throw new \RuntimeException($result->rejectionReason);
}
if ($result->hadThreats()) {
// The disarmed bytes are safe to render. Each removed threat carries its
// type and object number for your audit log — never silently.
foreach ($result->removedThreats as $threat) {
error_log(\sprintf(
'CDR removed %s in object %d',
$threat->type->value,
$threat->objectNumber,
));
}
}
$cleanBytes = $result->sanitizedPdf; // The security projection. Not the original.

لا يوجد مسار تسلّم فيه هذه الشيفرة ملفاً لا يزال خطيراً بهدوء. فإما أن يُقبَل المستند ويُنزَع سلاحه، أو يُرفَض بسبب مذكور. قائمة removedThreats تعني أن النزع قابل للتدقيق، لا سحري.

“CDR مجرد تنقيح بخطوات إضافية.”

ليس كذلك، والخلط بينهما خطير. يزيل التنقيح المعلومات — الأسماء، وأرقام الحسابات، والمحتوى الذي يجب ألا يراه إنسان. ويزيل CDR القدرة — الـ⁨JavaScript⁩، وإجراء ⁨Launch⁩، والحمولة المُضمَّنة التي يجب ألا تشغّلها آلة. لهما معايير نجاح متعاكسة. التنقيح صحيح حين يزول المحتوى الحساس ويُحفَظ الباقي حرفياً. والنزع صحيح حين يزول التهديد، وهو مستعد تماماً لتغيير بنية حميدة للوصول إلى ذلك. استخدم الأداة التي تطابق قصدك؛ لا تلجأ إلى إحداهما متوقعاً ضمانات الأخرى.

مفهوم خاطئ ثانٍ هو أن إعادة بناء نظيفة تثبت أن الأصل كان نظيفاً. إنها لا تثبت شيئاً عن الأصل. تثبت فقط أن المُخرَج لا يحوي تهديداً مكتشَفاً. ربما كان المُدخَل سلاحاً؛ ومهمة CDR أن تضمن أن ما تمرّره في مرحلة لاحقة ليس كذلك.

هذا هو الجزء الذي تتخطّاه الكتيّبات الدعائية، فسنقوله صراحةً.

  • المُخرَج إسقاط أمني، لا نسخة إثباتية. يحمل مصدر CdrEngine هذا خطاً أحمر معمارياً. ملف ⁨PDF⁩ المُطهَّر يجب ألا يُستخدَم لحفظ أدلة قانونية، ولا لمقارنة تجزئة مع الأصل، ولا نسخة أرشفة. التحويل مُتلِف وغير عكوس بالتصميم.
  • للاكتشاف نطاق. يزيل CDR التهديدات التي يعرف كيف يسمّيها. إنه طبقة قوية قابلة للتدقيق في حِزمة دفاع-في-عمق — لا ضمان بأن ملفاً خالٍ من كل تقنية مستقبلية ممكنة. أبقِه خلف التحقق من الرفع نفسه، وفحوص نوع المحتوى، ومعالجة الامتياز الأدنى التي يصفها إرشاد OWASP لرفع الملفات.
  • بعض السياسات مُتلِفة عمداً. حالات Strip* الاختيارية تزيل الملفات المُضمَّنة، والتوقيعات، وحقول النماذج، والطبقات، والوسائط ثلاثية الأبعاد. تلك قوية وستحذف محتوى مشروعاً — حمولة فاتورة ⁨ZUGFeRD⁩/⁨Factur-X⁩ مثلاً. وهي مُطفأة افتراضياً لذلك السبب بالضبط. شغّلها عن علم.
  • التوقيعات لا تنجو من إعادة البناء. إعادة بناء الملف تغيّر بايتاته، فأي توقيع رقمي أصلي لا يعود يطابق نطاق بايتاته. المستند المنزوع سلاحه غير موقَّع بالنسبة للمصدر. إن احتجت إلى أثرٍ موقَّع، فوقّع المُخرَج النظيف عملاً جديداً.
Content Disarm & Reconstruction (CDR) — edition availability
EditionAvailability
Core

غير متاح. نواة ⁨NextPDF⁩ لا تؤدي CDR. تحلّل ملفات ⁨PDF⁩ وتعرضها وتكتبها؛ لا تكتشف التهديدات ولا تعيد بناء مُدخَل غير موثوق.

Pro

غير متاح في إصدار ⁨Pro⁩.

Enterprise

متاح عبر CdrEngine. المُخرَج المنزوع سلاحه إسقاط أمني للمصدر — يجب ألا يُعامَل نسخةً إثباتيةً أو قابلةً لمقارنة التجزئة أو أرشيفيةً للأصل.

  • CDR (نزع المحتوى وإعادة بنائه Content Disarm & Reconstruction) — استراتيجية تطهير تحلّل ملفاً غير موثوق، وتزيل المكوّنات النشطة أو الخطيرة، وتعيد بناء ملف نظيف من الباقي الآمن، بدلاً من الفحص بحثاً عن توقيعات معروفة السوء.
  • الإسقاط الأمني — مُخرَج مُطهَّر يحفظ قدراً كافياً من المصدر ليكون مفيداً مع ضمان إزالة التهديدات. وهو عمداً ليس وفياً للبايتات وغير صالح للأدلة أو التجزئة أو الأرشفة.
  • التحكم في القبول — البوابة السابقة للتطهير التي ترفض المستندات المتجاوزة لحدود الموارد (عدد الكائنات، عدد الصفحات، حجم الدفق المفكوك، نسبة الانتفاخ) قبل بدء أي عمل نزع.
  • الإجراء — بنية ⁨PDF⁩ تجعل شيئاً يحدث على مُحفِّز كفتح المستند أو تغيّر حقل؛ فهرس أنواع الإجراءات (Spec: ISO 32000-2, §12.6.4) يشمل إجراءات ⁨JavaScript⁩ و⁨Launch⁩، سطح تهديد CDR النموذجي.
  • المرجع المعلَّق — مؤشر إلى كائن لم يعد موجوداً بعد الترشيح. تزيل مرحلة تنظيف-المراجع هذه كي يبقى الملف المُعاد بناؤه متسقاً بنيوياً.