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

يتم استخدام 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

دعم المتصفح

  • Chrome: غير متاح.
  • Edge: غير متوافقة
  • متصفّح Firefox: خلف علم
  • Safari: غير متاح.

المصدر

التفعيل من خلال خيار 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 من Mike West:

المراجع


صورة من إعداد توفيق بشاربايا على قناة Unقلاش