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

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- إطلاق نية الشحن على الإصدار M105

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

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

كيفية تفعيل Sanitizer API

Browser Support

  • Chrome: 146.
  • Edge: 146.
  • Firefox: 148.
  • Safari: not supported.

Source

يعمل Chrome على تنفيذ Sanitizer API. في Chrome 93 أو الإصدارات الأحدث، يمكنك تجربة السلوك من خلال تفعيل العلامة 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 من Mike West:

المراجع