معالجة DOM الآمن باستخدام واجهة برمجة تطبيقات Sanitizer

تهدف واجهة برمجة التطبيقات Sanitizer API الجديدة إلى إنشاء معالج قوي لسلاسل عشوائية يتم إدراجها بأمان في الصفحة.

Jack J
Jack J

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

للحدّ من هذا الخطر، يهدف اقتراح Sanitizer API الجديد إلى إنشاء معالج قوي لسلاسل عشوائية يتم إدراجها بأمان في الصفحة. تعرِض هذه المقالة واجهة برمجة التطبيقات وتوضّح كيفية استخدامها.

// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())

ترميز بيانات المستخدم

عند إدراج إدخال المستخدم وسلاسل طلبات البحث ومحتوى ملفات تعريف الارتباط وما إلى ذلك في نموذج DOM، يجب إلغاء ترميز السلاسل بشكل صحيح. يجب إيلاء اهتمام خاص بعمليات التلاعب بـ DOM من خلال .innerHTML، حيث تكون السلاسل غير المشفَّرة مصدرًا شائعًا لهجوم XSS.

const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input

في حال إلغاء ترميز أحرف HTML الخاصة في سلسلة الإدخال أعلاه أو توسيعها باستخدام .textContent، لن يتم تنفيذ alert(0). ومع ذلك، بما أنّ الرمز <em> الذي أضافه المستخدم يتم توسيعه أيضًا كسلسلة كما هو، لا يمكن استخدام هذه الطريقة للحفاظ على زخرفة النص في HTML.

إنّ أفضل إجراء يمكنك اتّخاذه هنا هو التطهير وليس الاستبدال.

تنقيح بيانات المستخدم

الفرق بين إزالة البيانات وتنظيفها

يشير ترميز الهروب إلى استبدال أحرف HTML الخاصة بوحدات HTML.

يشير التنظيف إلى إزالة الأجزاء الضارة دلاليًا (مثل تنفيذ النصوص البرمجية) من سلاسل HTML.

مثال

في المثال السابق، يؤدي <img onerror> إلى تنفيذ معالِج الخطأ، ولكن في حال إزالة معالِج onerror، سيكون من الممكن توسيعه بأمان في DOM مع إبقاء <em> سليمًا.

// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`

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

تهدف مواصفات Sanitizer API المقترَحة إلى توفير هذه المعالجة كواجهة برمجة تطبيقات عادية للمتصفّحات.

Sanitizer API

يتم استخدام واجهة برمجة التطبيقات Sanitizer API على النحو التالي:

const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>

ومع ذلك، فإنّ { sanitizer: new Sanitizer() } هي الوسيطة التلقائية. ويمكن أن يكون بالشكل الموضّح أدناه.

$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>

تجدر الإشارة إلى أنّه تمّ تحديد setHTML() على Element. بما أنّها طريقة Element، يكون السياق الذي يجب تحليله واضحًا من تلقاء نفسه (<div> في هذه الحالة)، ويتم إجراء التحليل مرة واحدة داخليًا، ويتم توسيع النطاق مباشرةً في بنية DOM.

للحصول على نتيجة عملية التنظيف كسلسلة، يمكنك استخدام .innerHTML من نتائج setHTML().

const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">

التخصيص من خلال الضبط

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

const config = {
  allowElements: [],
  blockElements: [],
  dropElements: [],
  allowAttributes: {},
  dropAttributes: {},
  allowCustomElements: true,
  allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)

تحدِّد الخيارات التالية كيفية معالجة نتيجة التطهير للعنصر المحدَّد.

allowElements: أسماء العناصر التي يجب أن يحتفظ بها المعقم

blockElements: أسماء العناصر التي يجب أن يزيلها المعقم مع الاحتفاظ بعناصرها الفرعية

dropElements: أسماء العناصر التي يجب أن يزيلها برنامج التعقيم، بالإضافة إلى عناصرها الفرعية

const str = `hello <b><i>world</i></b>`

$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>

$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>

يمكنك أيضًا التحكّم في ما إذا كان برنامج التطهير سيسمح بالسمات المحدّدة أو يرفضها باستخدام الخيارات التالية:

  • allowAttributes
  • dropAttributes

تتوقع السمتان allowAttributes وdropAttributes قوائم مطابقة السمات، وهي عناصر مفاتيحها هي أسماء السمات، والقيم هي قوائم بالعناصر المستهدَفة أو العنصر النائب *.

const str = `<span id=foo class=bar style="color: red">hello</span>`

$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>

allowCustomElements هو خيار السماح بالعناصر المخصّصة أو رفضها. في حال السماح بها، ستظل الإعدادات الأخرى للعناصر والسمات سارية.

const str = `<custom-elem>hello</custom-elem>`

$div.setHTML(str)
// <div></div>

const sanitizer = new Sanitizer({
  allowCustomElements: true,
  allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>

واجهة برمجة التطبيقات

مقارنةً بـ DomPurify

DOMPurify هي مكتبة معروفة توفّر وظيفة التطهير. الفرق الرئيسي بين Sanitizer API وDOMPurify هو أنّ DOMPurify يعرض نتيجة عملية التطهير كسلسلة، وعليك كتابتها في عنصر DOM من خلال .innerHTML.

const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`

يمكن أن يكون DOMPurify بديلاً في حال عدم تنفيذ واجهة برمجة التطبيقات Sanitizer API في المتصفّح.

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

يحتاج تنسيق HTML أيضًا إلى سياق لتحليله. على سبيل المثال، يكون <td> منطقيًا في <table>، ولكن ليس في <div>. بما أنّ دالة DOMPurify.sanitize() لا تقبل سوى سلسلة كوسيطة، كان من الضروري تخمين سياق التحليل.

تحسِّن Sanitizer API من نهج DOMPurify وهي مصمّمة لإزالة الحاجة إلى التحليل المزدوج وتوضيح سياق التحليل.

حالة واجهة برمجة التطبيقات وتوافق المتصفّح

لا تزال واجهة برمجة التطبيقات Sanitizer API قيد المناقشة في عملية التوحيد، ويعمل فريق Chrome على تنفيذها.

الخطوة الحالة
1. إنشاء فيديو توضيحي مكتمل
2. إنشاء مسودة المواصفات مكتمل
3- جمع الملاحظات وتحسين التصميم مكتمل
4. مرحلة التجربة والتقييم في Chrome مكتمل
5- إطلاق Intent to Ship on M105

Mozilla: ترى أنّ هذا الاقتراح جدير بإنشاء نماذج أولية له، وتنفّذه بنشاط.

WebKit: يمكنك الاطّلاع على الردّ في قائمة بريد WebKit.

كيفية تفعيل واجهة برمجة التطبيقات Sanitizer API

التفعيل من خلال about://flags أو خيار واجهة سطر الأوامر

Chrome

يجري Chrome عملية تنفيذ واجهة برمجة التطبيقات Sanitizer API. في الإصدار 93 من Chrome أو الإصدارات الأحدث، يمكنك تجربة السلوك من خلال تفعيل العلامة about://flags/#enable-experimental-web-platform-features. في الإصدارات السابقة من Chrome Canary وقناة المطوّرين، يمكنك تفعيلها من خلال --enable-blink-features=SanitizerAPI وتجربتها الآن. اطّلِع على تعليمات حول كيفية تشغيل Chrome باستخدام علامات.

Firefox

ينفِّذ Firefox أيضًا واجهة برمجة التطبيقات Sanitizer API كميزة تجريبية. لتفعيل هذه الميزة، اضبط العلامة dom.security.sanitizer.enabled على true في about:config.

رصد الميزات

if (window.Sanitizer) {
  // Sanitizer API is enabled
}

ملاحظات

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

إذا رصدت أي أخطاء أو سلوكًا غير متوقّع في عملية تنفيذ Chrome، يُرجى إرسال تقرير عن الخطأ. اختَر مكوّنات Blink>SecurityFeature>SanitizerAPI وشارِك التفاصيل لمساعدة موظّفي التنفيذ في تتبُّع المشكلة.

عرض توضيحي

للاطّلاع على واجهة برمجة التطبيقات Sanitizer API أثناء استخدامها، يمكنك الاطّلاع على Sanitizer API Playground من إعداد مايك ويست:

المراجع


الصورة مقدمة من Towfiqu barbhuiya على Unsplash.