العب بأمان في إطارات IFrames المتوافقة مع وضع الحماية

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

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

أدنى امتياز

من حيث الجوهر، نحن نبحث عن آلية تمكّننا من منح المحتوى الذي نضمّنه فقط الحد الأدنى من مستوى الإمكانيات اللازمة لأداء مهمته. إذا لم تكن الأداة بحاجة إلى فتح نافذة جديدة، فإن إزالة إمكانية الوصول إلى window.open لا يمكن أن يضر. وإذا لم يكن الأمر يتطلب فلاش، لن يمثل إيقاف دعم المكون الإضافي مشكلة. سنظل أكثر أمانًا قدر الإمكان في حال طبّقنا مبدأ أقل الامتيازات، وحظرنا كل ميزة لا صلة لها بشكل مباشر بالوظيفة التي نريد استخدامها. ونتيجة لذلك، لم يعد علينا الوثوق بشكل عميق في أنّ بعض المحتوى المضمّن لن يستفيد من الامتيازات التي لا يجب استخدامها. لن يكون بإمكانه الوصول إلى الوظيفة في المقام الأول.

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

تمنحنا السمة sandbox للعنصر iframe ما نحتاجه لتشديد القيود على المحتوى المؤطر. يمكننا توجيه المتصفح لتحميل محتوى إطار معين في بيئة ذات امتياز منخفض، ما يسمح فقط بمجموعة فرعية من الإمكانات اللازمة للقيام بأي عمل يحتاج إليه.

يجب إثبات صحّتك

يُعد زر "تغريد" في Twitter مثالاً رائعًا على الوظائف التي يمكن تضمينها بشكل أكثر أمانًا في موقعك عبر وضع الحماية. يتيح لك Twitter تضمين الزرّ عبر iframe باستخدام الرمز التالي:

<iframe src="https://platform.twitter.com/widgets/tweet_button.html"
        style="border: 0; width:130px; height:20px;"></iframe>

لمعرفة ما يمكننا تأمينه، لنفحص بعناية الإمكانيات التي يتطلبها الزر. وينفّذ رمز HTML الذي يتم تحميله في الإطار مقتطفًا من JavaScript من خوادم Twitter، وينشئ نافذة منبثقة مليئة بواجهة تغريد عند النقر عليها. تحتاج تلك الواجهة إلى الوصول إلى ملفات تعريف الارتباط على Twitter لربط التغريدة بالحساب الصحيح، كما تحتاج إلى القدرة على إرسال نموذج التغريدات. هذا كل شيء تقريبًا؛ لا يحتاج الإطار إلى تحميل أي مكونات إضافية، ولا يحتاج إلى التنقل في نافذة المستوى الأعلى، أو إلى أي عدد من الوظائف الأخرى. نظرًا لأنه لا يحتاج إلى هذه الامتيازات، فدعنا نزيلها من خلال وضع حماية محتوى الإطار.

يعمل وضع الحماية على أساس القائمة البيضاء. نبدأ بإزالة جميع الأذونات الممكنة، ثم إعادة تشغيل الإمكانات الفردية من خلال إضافة علامات محددة إلى تهيئة وضع الحماية. بالنسبة إلى أداة Twitter، فقد قررنا تفعيل JavaScript والنوافذ المنبثقة وإرسال النموذج وملفات تعريف الارتباط على twitter.com. يمكننا إجراء ذلك من خلال إضافة سمة sandbox إلى iframe مع القيمة التالية:

<iframe sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
    src="https://platform.twitter.com/widgets/tweet_button.html"
    style="border: 0; width:130px; height:20px;"></iframe>

ما مِن إجراءات أخرى مطلوبة. لقد منحنا الإطار جميع الإمكانات التي يحتاجها، وسيمنع المتصفّح بشكل مفيد الوصول إلى أي من الامتيازات التي لم نمنحها صراحةً من خلال قيمة السمة sandbox.

مراقبة دقيقة في الإمكانات

لقد رأينا بعض علامات وضع الحماية المحتملة في المثال أعلاه، لنتعمق الآن في الأعمال الداخلية للسمة بمزيد من التفصيل.

وبالنظر إلى إطار iframe مع سمة وضع حماية فارغة، ستتم إضافة وضع الحماية للمستند بشكل كامل، مع إخضاعه للقيود التالية:

  • لن يتم تنفيذ JavaScript في المستند المؤطر. ولا يتضمن ذلك JavaScript التي تم تحميلها بشكل صريح عبر علامات النصوص البرمجية فحسب، بل يتضمن أيضًا معالِجات الأحداث المضمَّنة وJavaScript: عناوين URL. وهذا يعني أيضًا أنه سيتم عرض المحتوى المُضمَّن في علامات noscript، تمامًا كما لو كان المستخدم قد أوقف النص البرمجي بنفسه.
  • يتم تحميل المستند المؤطر في مصدر فريد، ما يعني أنّ جميع عمليات التحقّق من المصدر نفسه لن تنجح. لا تتطابق المصادر الفريدة مع أي مصادر أخرى على الإطلاق، حتى نفسها. ويعني هذا، ضمن تأثيرات أخرى، أنّه لا يمكن للمستند الوصول إلى البيانات المخزَّنة في ملفات تعريف الارتباط لأي مصدر أو أي آليات تخزين أخرى (مساحة تخزين DOM وقاعدة البيانات المفهرَسة وما إلى ذلك).
  • لا يمكن للمستند المُحاط بإطار إنشاء نوافذ أو مربّعات حوار جديدة (عبر window.open أو target="_blank"، على سبيل المثال).
  • يتعذّر إرسال النماذج.
  • لن يتم تحميل المكوّنات الإضافية.
  • ويمكن للمستند المُحاط بإطار أن يتنقل بنفسه فقط، وليس المستند الرئيسي ذي المستوى الأعلى. وسيؤدي الإعداد window.top.location إلى إنشاء استثناء، ولن يكون للنقر على الرابط باستخدام target="_top" أي تأثير.
  • يتم حظر الميزات التي يتم تشغيلها تلقائيًا (عناصر النموذج التي يتم التركيز عليها تلقائيًا، والفيديوهات التي يتم تشغيلها تلقائيًا، وغير ذلك).
  • يتعذّر الحصول على قفل المؤشر.
  • يتم تجاهل السمة seamless في iframes الذي يحتوي عليه المستند المؤطر.

هذا تصرف قوي للغاية، والمستند الذي يتم تحميله في iframe خاضع للحماية بالكامل إلى خطر ضئيل جدًا. وبالطبع، لا فائدة منها أيضًا: قد تتمكن من استخدام وضع الحماية الكامل لبعض المحتوى الثابت، ولكن في معظم الأحيان ستحتاج إلى تخفيف الأمور قليلاً.

باستثناء المكوّنات الإضافية، يمكن رفع كل من هذه القيود عن طريق إضافة علامة إلى قيمة سمة وضع الحماية. لا يمكن للمستندات المحمية بوضع الحماية مطلقًا تشغيل المكونات الإضافية، لأن المكونات الإضافية هي رموز برمجية أصلية غير محمية، ولكن كل شيء آخر يعد لعبة عادلة:

  • تسمح allow-forms بإرسال النموذج.
  • يسمح allow-popups بالنوافذ المنبثقة (الصدمة).
  • يسمح allow-pointer-lock بقفل المؤشر (مفاجأة).
  • تسمح سياسة allow-same-origin للمستند بالحفاظ على مصدره، وستحتفظ الصفحات التي تم تحميلها من https://example.com/ بإمكانية الوصول إلى بيانات ذلك المصدر.
  • تتيح allow-scripts تنفيذ JavaScript، كما تسمح للميزات بتشغيلها تلقائيًا (لأنّ تنفيذها عبر JavaScript سيكون بسيطًا).
  • يتيح allow-top-navigation للمستند الخروج من الإطار من خلال التنقل في نافذة المستوى الأعلى.

مع وضع ذلك في الاعتبار، يمكننا تقييم السبب بالضبط الذي انتهى به الأمر إلى مجموعة محددة من علامات وضع الحماية في مثال Twitter أعلاه:

  • السمة allow-scripts مطلوبة، لأنّ الصفحة التي يتم تحميلها في الإطار تُشغّل بعض JavaScript للتعامل مع تفاعل المستخدم.
  • يجب استخدام allow-popups، لأنّ الزر ينبثق عن نموذج تغريدة في نافذة جديدة.
  • يجب إدخال السمة allow-forms، لأنّه يجب أن يكون نموذج التغريدة قابلاً للإرسال.
  • allow-same-origin ضروري، إذ يتعذر الوصول إلى ملفات تعريف الارتباط على twitter.com، ولن يتمكن المستخدم من تسجيل الدخول لنشر النموذج.

هناك شيء مهم يجب ملاحظته وهو أن علامات وضع الحماية المطبقة على الإطار تنطبق أيضًا على أي نوافذ أو إطارات تم إنشاؤها في وضع الحماية. يعني ذلك أنّه علينا إضافة allow-forms إلى وضع الحماية للإطار، على الرغم من أنّ النموذج متوفّر فقط في النافذة التي ينبثقها الإطار.

عند توفّر السمة sandbox، تحصل الأداة على الأذونات التي تطلبها فقط، وتظل إمكانات مثل المكوّنات الإضافية والتنقل العلوي وقفل المؤشر محظورة. لقد قللنا من خطر تضمين التطبيق المصغّر بدون أي تأثيرات سيئة. فهذا مكسب لكل الأطراف المعنية.

فصل الأذونات

من الواضح أنّ استخدام وضع الحماية للمحتوى التابع لجهة خارجية من أجل تشغيل رمزه غير الموثوق به في بيئة ذات امتياز أقل هو أمر مفيد للغاية. ولكن ماذا عن التعليمات البرمجية؟ أنت تثق بنفسك، أليس كذلك؟ فلا داعي للقلق بشأن وضع الحماية؟

أود أن أحول هذا السؤال: إذا كانت التعليمات البرمجية الخاصة بك لا تحتاج إلى مكونات إضافية، فلماذا تمنحها الوصول إلى المكونات الإضافية؟ وفي أفضل الأحوال، يكون ذلك امتيازًا لا تستخدمه مطلقًا، وفي أسوأ الأحوال، يمكن أن يكون متجهًا محتملاً للمهاجمين للهجوم على هجمات المهاجمين. تحتوي التعليمات البرمجية للجميع على أخطاء، ويكون كل تطبيق عمليًا عرضة للاستغلال بطريقة أو بأخرى. يعني وضع الحماية للرمز البرمجي الخاص بك أنه حتى إذا نجح أحد المهاجمين في إبطال تطبيقك، لن يحصل على حق الوصول الكامل إلى أصل التطبيق، بل سيتمكن فقط من تنفيذ الإجراءات التي يمكن للتطبيق تنفيذها.

يمكنك تقليل المخاطر أكثر عن طريق تقسيم تطبيقك إلى أجزاء منطقية ووضع الحماية لكل جزء بأقل امتياز ممكن. هذه التقنية شائعة جدًا في الرموز البرمجية الأصلية: على سبيل المثال، يقسّم Chrome نفسه إلى عملية متصفّح ذات امتياز خاص بها يمكنها الوصول إلى محرك الأقراص الثابتة المحلي وإجراء اتصالات بالشبكة، والعديد من عمليات العرض ذات الامتيازات المنخفضة التي تؤدي إلى الكثير من عبء تحليل المحتوى غير الموثوق به. لا تحتاج برامج العرض إلى لمس القرص، إذ يعتني المتصفح بتزويدهم بجميع المعلومات التي يحتاجونها لعرض الصفحة. وحتى إذا وجد متطفلة ماهرة طريقة لإفساد العارض، فإنّها لم تصِل إلى أقصى حدّ، حيث لا يكون لدى العارض أي فائدة كبيرة من تلقاء نفسه: يجب توجيه جميع أذونات الوصول ذات الامتياز العالي من خلال إجراءات المتصفح. سيحتاج المهاجمون إلى العثور على عدة ثقوب في أجزاء مختلفة من النظام لإلحاق أي ضرر، ما يقلل إلى حد كبير من مخاطر عملية التعمير الناجحة.

استخدام وضع الحماية الآمن لتطبيق "eval()"

باستخدام وضع الحماية وpostMessage API، يمكن تطبيق نجاح هذا النموذج على الويب مباشرةً. ويمكن أن تكون بعض أجزاء تطبيقك مثبّتة على iframe في وضع الحماية، كما يمكن للمستند الرئيسي أن يعطّل التواصل بينها من خلال نشر الرسائل والاستماع إلى الردود. ويضمن هذا النوع من الهياكل أن عمليات الاستغلال في أي جزء من التطبيق تؤدي إلى أقل قدر ممكن من الضرر. كما أن له ميزة إجبارك على إنشاء نقاط دمج واضحة، حتى تعرف بالضبط أين تحتاج إلى توخي الحذر بشأن التحقق من صحة المدخلات والمخرجات. دعونا نتعرف على مثال لعبة، فقط لنرى كيف يمكن أن يعمل ذلك.

Evalbox هو تطبيق مثير يأخذ سلسلة ويقيّمها على أنها JavaScript. رائع، أليس كذلك؟ بالضبط ما كنت تنتظره طوال هذه السنوات الطويلة. إنّه تطبيق خطير إلى حد ما، إذ إنّ السماح بتشغيل JavaScript عشوائي يعني أنّ جميع البيانات المتوفرة في المصدر جاهزة للاستلام. سنخفف من خطر حدوث Bad ThingsTM من خلال ضمان تنفيذ الرمز البرمجي داخل وضع الحماية، ما يجعله أكثر أمانًا. سنعمل في طريقنا من خلال التعليمات البرمجية من الداخل، بدءًا من محتويات الإطار:

<!-- frame.html -->
<!DOCTYPE html>
<html>
    <head>
    <title>Evalbox's Frame</title>
    <script>
        window.addEventListener('message', function (e) {
        var mainWindow = e.source;
        var result = '';
        try {
            result = eval(e.data);
        } catch (e) {
            result = 'eval() threw an exception.';
        }
        mainWindow.postMessage(result, event.origin);
        });
    </script>
    </head>
</html>

وبداخل الإطار، يتوفَّر لدينا مستند بسيط يستمع إلى الرسائل من مصدره الرئيسي من خلال الربط بحدث message للكائن window. عندما ينفّذ العنصر الرئيسي postMessage على محتوى iframe، سيتم تشغيل هذا الحدث، ما يمنحنا إمكانية الوصول إلى السلسلة التي يريد أحد الوالدَين تنفيذها.

في المعالج، نلتقط السمة source للحدث، وهي النافذة الرئيسية. سنستخدم هذا لإرسال نتيجة عملنا الشاق مرة أخرى بمجرد الانتهاء. بعد ذلك، سنتولّى المهام الصعبة من خلال تمرير البيانات التي تم تقديمها لنا إلى eval(). تم إنهاء هذا المكالمة في حظر المحاولة، لأنّ العمليات المحظورة داخل iframe في وضع الحماية تؤدي غالبًا إلى إنشاء استثناءات DOM. سنلتقط هذه الاستثناءات ونبلغ عن رسالة خطأ ودية بدلاً من ذلك. وأخيرًا، ننشر النتيجة مرة أخرى إلى النافذة الأصلية. هذا شيء بسيط جدًا.

الأمر نفسه ليس معقدًا كذلك. سننشئ واجهة مستخدم صغيرة تحتوي على textarea للرمز البرمجي وbutton للتنفيذ، وسنضيف frame.html عبر iframe في وضع الحماية، ما يسمح بتنفيذ النص البرمجي فقط:

<textarea id='code'></textarea>
<button id='safe'>eval() in a sandboxed frame.</button>
<iframe sandbox='allow-scripts'
        id='sandboxed'
        src='frame.html'></iframe>

والآن سنحل المشكلة للتنفيذ. أولاً، سنستمع إلى الردود من "iframe" و"alert()" إلى المستخدمين. ومن المفترض أن يؤدي التطبيق الحقيقي إلى شيء أقل إزعاجًا:

window.addEventListener('message',
    function (e) {
        // Sandboxed iframes which lack the 'allow-same-origin'
        // header have "null" rather than a valid origin. This means you still
        // have to be careful about accepting data via the messaging API you
        // create. Check that source, and validate those inputs!
        var frame = document.getElementById('sandboxed');
        if (e.origin === "null" &amp;&amp; e.source === frame.contentWindow)
        alert('Result: ' + e.data);
    });

بعد ذلك، سنربط معالج أحداث بالنقرات على button. عندما ينقر المستخدم، سنحصل على المحتوى الحالي في textarea ونمرره إلى الإطار لتنفيذه:

function evaluate() {
    var frame = document.getElementById('sandboxed');
    var code = document.getElementById('code').value;
    // Note that we're sending the message to "*", rather than some specific
    // origin. Sandboxed iframes which lack the 'allow-same-origin' header
    // don't have an origin which you can target: you'll have to send to any
    // origin, which might alow some esoteric attacks. Validate your output!
    frame.contentWindow.postMessage(code, '*');
}

document.getElementById('safe').addEventListener('click', evaluate);

الأمر سهل، أليس كذلك؟ لقد أنشأنا واجهة برمجة تطبيقات للتقييم بسيطة للغاية، ويمكننا التأكد من أن الرمز الذي تم تقييمه لا يمكنه الوصول إلى المعلومات الحساسة مثل ملفات تعريف الارتباط أو تخزين DOM. وبالمثل، لا يمكن للرمز الذي تم تقييمه تحميل مكونات إضافية أو نوافذ منبثقة جديدة أو أي عدد من الأنشطة المزعجة أو الضارة الأخرى.

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

مع ذلك، يجب توخي الحذر الشديد عند التعامل مع محتوى بإطارات يأتي من نفس المصدر الأصلي. إذا كانت هناك صفحة على https://example.com/ إطارًا لصفحة أخرى على الأصل نفسه باستخدام وضع الحماية الذي يتضمّن العلامتَين allow-same-origin وallow-scripts، يمكن للصفحة المؤطرة الوصول إلى العنصر الرئيسي وإزالة سمة وضع الحماية بالكامل.

استمتِع باللعب في وضع الحماية

تتوفر ميزة "وضع الحماية" الآن في مجموعة متنوعة من المتصفحات مثل: Firefox 17 والإصدارات الأحدث، وIE10 والإصدارات الأحدث، وChrome في وقت كتابة هذا التقرير (تحتوي منصة caniuse بالطبع على جدول دعم محدّث). إنّ تطبيق السمة sandbox على iframes الذي تدرجه يتيح لك منح امتيازات معيّنة للمحتوى الذي يعرضه، وفقط الامتيازات اللازمة لعمل المحتوى بشكل صحيح. يتيح لك ذلك تقليل المخاطر المرتبطة بتضمين محتوى تابع لجهة خارجية، بما يتجاوز حدود سياسة أمان المحتوى.

علاوة على ذلك، يعد وضع الحماية أسلوبًا قويًا للحد من خطر أن يكون المهاجم الذكي قادرًا على استغلال الثقوب الموجودة في التعليمات البرمجية الخاصة بك. من خلال فصل تطبيق متجانس إلى مجموعة من الخدمات الموضوعة في وضع الحماية، كل خدمة مسؤولة عن جزء صغير من الوظائف المستقلة، سيضطر المهاجمون إلى عدم اختراق محتوى إطارات معينة فحسب، بل يجب أيضًا اختراق وحدة التحكم لديهم. وهذه مهمة أكثر صعوبة، خاصةً وأن وحدة التحكم يمكن أن تقلل بشكل كبير من نطاقها. يمكنك بذل جهدك في مجال الأمان لمراجعة هذا الرمز إذا طلبت المساعدة من المتصفّح.

هذا لا يعني أنّ وضع الحماية هو حل كامل لمشكلة الأمان على الإنترنت. فهو يقدم دفاعًا متعمقًا، ما لم تكن تستطيع التحكم في عملاء المستخدمين، فلا يمكنك حتى الآن الاعتماد على دعم المتصفح لجميع المستخدمين (إذا كنت تتحكم في عملاء المستخدمين -- بيئة مؤسسية، على سبيل المثال -- يا للروعة!). يومًا ما... ولكن في الوقت الحالي، أصبح وضع الحماية طبقة حماية أخرى لتقوية دفاعاتك، وهو ليس دفاعًا كاملاً يمكنك الاعتماد عليه فقط. لا تزال الطبقات ممتازة. أقترح الاستفادة من هذا.

قراءات إضافية

  • "فصل الامتيازات في تطبيقات HTML5" هو بحث مثير للاهتمام يعمل من خلال تصميم إطار عمل صغير وتطبيقه على ثلاثة تطبيقات HTML5 حالية.

  • ويمكن أن يكون وضع الحماية أكثر مرونة عند دمجه مع سمتَين جديدتَين لإطار iframe هما: srcdoc وseamless. يسمح لك العنصر الأول بتعبئة إطار بمحتوى بدون استخدام طلب HTTP، ويسمح الأخير بالتدفق إلى المحتوى داخل الإطار. في الوقت الحالي، لا شكّ في أنّ عمليتَي Chrome وWebKit لا يبطلان من نتائج البحث، إلا أنّ هاتين الميزتَين ستكونان مشوّقة في المستقبل. يمكنك، على سبيل المثال، وضع تعليقات وضع الحماية على مقالة عبر التعليمة البرمجية التالية:

        <iframe sandbox seamless
                srcdoc="<p>This is a user's comment!
                           It can't execute script!
                           Hooray for safety!</p>"></iframe>