معاينة المواقع الخارجية بأمان — تنفيذ الوكيل (Proxy) + التعقيم + عزل iframe sandbox

HeatMapX Engineering Team4 min read
  • engineering
  • security
  • nextjs
  • iframe

ملخص هذا المقال

  • تحميل موقع خارجي داخل iframe من نطاقنا الخاص ينطوي على خطر أن تتمكن جافاسكريبت خارجية من التأثير على جلستنا الخاصة
  • الحل هو دفاع متعدد الطبقات: "وكيل (proxy) مزوّد بحماية من SSRF + إزالة الجافاسكريبت الخارجية + سياسة CSP مقيّدة + iframe sandbox"
  • إذا كان sandbox قويًا أكثر من اللازم فإنه يوقف حتى سكريبتاتنا الخاصة؛ ونسيان إضافة allow-scripts هو أحد هذه الأخطاء الشائعة

تتضمن اختبارات A/B في HeatMapX ميزة "معاينة شكل النسخة المعدَّلة (Variant B) بعد تطبيقها على الصفحة المستهدفة". من الناحية التقنية، هذه العملية هي في جوهرها "عرض موقع المستخدم الخارجي بأمان داخل iframe من نطاقنا الخاص" — وهي مسألة حساسة أكثر مما تبدو. نشارك هنا التصميم والفخ الفعلي الذي وقعنا فيه.

لماذا يشكّل التنفيذ الساذج خطرًا

إذا جلبنا HTML الخاص بالموقع الخارجي كما هو وعرضناه من نطاقنا الخاص (مثل heatmapx.com)، فإن أي عناصر <script> أو onclick= موجودة داخل ذلك الـ HTML ستُنفَّذ بصلاحيات نطاقنا الخاص. هذا يعني احتمال قراءة ملفات تعريف الارتباط (Cookies) أو جلسة تسجيل الدخول. وهذا أمر يجب تجنبه تمامًا.

بنية الدفاع متعدد الطبقات

لذلك نعتمد على الطبقات التالية مجتمعة.

1. وكيل (Proxy) مزوّد بحماية من SSRF

نتحقق من صحة رابط الجلب، ونرفض أي بروتوكول غير http/https. كما نرفض localhost، وعناوين IP الخاصة، ونقاط بيانات التعريف الخاصة بخدمات السحابة (مثل 169.254.169.254). ولا نتبع عمليات إعادة التوجيه (redirect) تلقائيًا، بل نستخدم redirect: 'manual' للتحقق من كل خطوة على حدة، لمنع أي توجيه نحو الشبكة الداخلية.

2. إزالة الجافاسكريبت الخارجية (التعقيم)

نزيل من HTML الذي تم جلبه وسوم <script> ومعالِجات الأحداث المضمّنة on*= وروابط بروتوكول javascript:. فلا حاجة لتشغيل جافاسكريبت الخاصة بموقع العميل في المعاينة (وعدم القدرة على إعادة إنتاج تطبيقات SPA بشكل كامل هو قيد معروف ومقبول).

3. سياسة CSP مقيّدة وعنصر base href

نُدرج <base href> مباشرة بعد <head> لحل مسارات الصور وملفات CSS النسبية، بينما نضيف إلى الاستجابة سياسة CSP مقيّدة تمنع تشغيل أي جافاسكريبت خارجية. الصور وملفات CSS مسموح بها من أجل العرض.

4. عزل iframe sandbox

وأخيرًا، نضيف خاصية sandbox إلى الـ iframe في جانب العرض لعزله.

الفخ الذي وقعنا فيه: sandbox كان قويًا أكثر من اللازم

تعمل المعاينة عبر حقن سكريبت صغير خاص بنا "يطبّق مجموعة التغييرات (changeSet)" داخل HTML الذي تم جلبه، ثم تشغيله داخل iframe لتغيير المظهر. لكن اتضح أن sandbox الخاص بـ iframe المعاينة كان يحتوي فقط على allow-same-origin، بدون allow-scripts.

والنتيجة كانت خللًا حيث لا يُنفَّذ سكريبتنا الخاص، فتظهر الصفحة الأصلية دون تطبيق أي تغيير. أما iframe الخاص بمحرر التعديل فكان يعمل بشكل صحيح بإعداد allow-scripts allow-same-origin، ما يعني أن السبب كان تباينًا في الإعدادات بين الاثنين.

كان الإصلاح بسيطًا: توحيد إعداد جانب المعاينة أيضًا ليصبح allow-scripts allow-same-origin. وأضفنا كذلك اختبار انحدار (regression test) يتحقق من خاصية sandbox في الـ iframe.

sandbox="allow-same-origin"  →  sandbox="allow-scripts allow-same-origin"

الدروس المستفادة

  • ينبغي التعامل مع تضمين المحتوى الخارجي على أساس "دفاع متعدد الطبقات"، دون الاعتماد على إجراء واحد فقط.
  • خاصية sandbox ليست "آمنة بمجرد إضافتها" — الأهم هو منح الصلاحيات اللازمة فقط بدقة. فإذا كانت قوية أكثر من اللازم، فقد توقف حتى وظائفنا الخاصة.
  • عند وجود "مسارين ينفذان نفس المعالجة" (المحرر والمعاينة هنا)، يجب دائمًا التأكد من توحيد خصائص الأمان بينهما. فإصلاح أحدهما فقط قد يكسر الآخر.

الخلاصة

يمكن تحقيق معاينة آمنة للمواقع الخارجية من خلال دفاع متعدد الطبقات يجمع بين الحماية من SSRF، وإزالة الجافاسكريبت الخارجية، وسياسة CSP مقيّدة، وiframe sandbox. وصلاحيات sandbox تسبب مشاكل سواء كانت غير كافية أو مفرطة. وما حدث معنا من نسيان allow-scripts كان مثالًا نموذجيًا على ذلك.

خرائط حرارية تُشغّل من Claude Code — ابدأ مجانًا.

ألصق وسم تتبع واحد، واحصل على التحليل واقتراحات CRO من سطر الأوامر.