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

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

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

الأقل امتيازًا

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

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

السمة 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.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، كما يسمح للميزات يتم تشغيلها تلقائيًا (لأنها ستكون بسيطة للتنفيذ عبر جافا سكريبت).
  • يسمح allow-top-navigation للمستند بالخروج من الإطار من خلال والتنقل في نافذة المستوى الأعلى.

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

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

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

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

فصل الامتيازات

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

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

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

وضع الحماية الآمن في 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، ثم يمكن أن تصل الصفحة ذات الإطار إلى الصفحة الرئيسية، وإزالة السمة sandbox كليًا.

اللعب في وضع الحماية

يتوفر وضع الحماية لك الآن في مجموعة متنوعة من المتصفحات: 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>