معالجة 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

يجري حاليًا تنفيذ واجهة برمجة تطبيقات Sanitizer في Chrome. في الإصدار 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
}

ملاحظات

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

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

عرض توضيحي

للاطّلاع على Sanitizer API أثناء العمل، يمكنك الاطّلاع على Sanitizer API Playground من إعداد مايك ويست:

المراجع


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