منع الثغرات الأمنية في البرمجة النصية للمواقع الإلكترونية المستندة إلى DOM باستخدام ميزة "الأنواع الموثوق بها"

Krzysztof Kotowicz
Krzysztof Kotowicz

توافق المتصفّح

  • Chrome: 83
  • ‫Edge: 83
  • Firefox: غير مدعوم.
  • Safari: غير متاح.

المصدر

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

DOM XSS هي إحدى أكثر الثغرات الأمنية شيوعًا على الويب، ومن الشائع أن تضعه فِرق التطوير عن طريق الخطأ في تطبيقاتهم. تمنحك الأنواع الموثوق بها الأدوات اللازمة لكتابة التطبيقات ومراجعتها من حيث الأمان والحفاظ عليها من ثغرات DOM XSS من خلال جعل وظائف واجهة برمجة التطبيقات الخطيرة آمنة بشكل تلقائي. وتتوفر "الأنواع الموثوق بها" على هيئة polyfill في المتصفحات التي لا تتوافق معها حتى الآن.

الخلفية

لسنوات عديدة، لطالما كان DOM XSS إحدى أكثر الثغرات الأمنية انتشارًا وخطورة على الويب.

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

لـ منع هجمات XSS من جهة الخادم، لا تنشئ صفحات HTML من خلال تسلسل سلاسل. استخدِم مكتبات النماذج الآمنة التي تتضمّن ميزة "الإخفاء التلقائي للمحتوى في سياقه" بدلاً من ذلك، بالإضافة إلى سياسة أمان المحتوى المستندة إلى مفتاح عشوائي لتجنّب الأخطاء بشكلٍ أكبر.

يمكن للمتصفّحات الآن أيضًا المساعدة في منع هجمات XSS المستندة إلى نموذج العناصر في المستند (DOM) من جهة العميل باستخدام الأنواع الموثوق بها.

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

تعمل Trusted Types من خلال قفل وظائف التصدير الخطيرة التالية. قد تكون على دراية ببعض هذه الميزات، لأنّ مورّدي المتصفّحات وأطر عمل الويب ينصحونك بعدم استخدامها لأسباب تتعلق بالأمان.

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

الإجراءات غير المُوصى بها
anElement.innerHTML  = location.href;
عند تفعيل Trusted Types، يُرسِل المتصفّح خطأ TypeError ويمنع استخدام عنصر DOM XSS مع سلسلة.

للإشارة إلى أنّه تمت معالجة البيانات بأمان، أنشئ عنصرًا خاصًا، وهو النوع الموثوق.

الإجراءات الموصى بها
anElement.innerHTML = aTrustedHTML;
  
عند تفعيل ميزة "الأنواع الموثوق بها"، يقبل المتصفّح كائن TrustedHTML للأحواض التي تتوقع مقتطفات HTML. هناك أيضًا عنصرَا TrustedScript وTrustedScriptURL لأنواع غيرها من المصارف الحسّاسة.

تقلِّل Trusted Types بشكل كبير من مساحة هجوم هجمات XSS المستندة إلى نموذج DOM في تطبيقك. ويبسّط هذا الإجراء مراجعات الأمان، ويتيح لك فرض عمليات التحقّق من الأمان المستندة إلى النوع التي يتم إجراؤها عند تجميع الرمز البرمجي أو فحصه أو تجميعه أثناء التشغيل في المتصفّح.

كيفية استخدام "الأنواع الموثوق بها"

الاستعداد لتلقّي تقارير انتهاكات "سياسة أمان المحتوى"

يمكنك نشر أداة تجميع التقارير، مثل Reporting-api-processor مفتوح المصدر أو go-csp-collector، أو استخدام إحدى المكافِئات التجارية. يمكنك أيضًا إضافة تسجيل مخصّص ورصد مخالفات تصحيح الأخطاء في المتصفّح باستخدام ReportingObserver:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

أو من خلال إضافة أداة معالجة الحدث:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

إضافة عنوان CSP لإعداد التقارير فقط

أضِف عنوان استجابة HTTP التالي إلى المستندات التي تريد نقلها إلى الأنواع الموثوق بها:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

يتم الآن الإبلاغ عن جميع الانتهاكات إلى //my-csp-endpoint.example، ولكن يستمر عمل الموقع الإلكتروني. يوضّح القسم التالي طريقة عمل //my-csp-endpoint.example.

تحديد انتهاكات Trusted Types

من الآن فصاعدًا، في كل مرة ترصد فيها ميزة "الأنواع الموثوق بها" انتهاكًا، يُرسِل المتصفّح ملف report-uri تم إعداده. على سبيل المثال، عندما يمرر تطبيقك سلسلة إلى innerHTML، يرسل المتصفّح التقرير التالي:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

وهذا يعني أنّه في https://my.url.example/script.js في السطر 39، تم استدعاء innerHTML بالسلسلة التي تبدأ بـ <img src=x. من المفترض أن تساعدك هذه المعلومات في تحديد أجزاء الرمز البرمجي التي قد تؤدي إلى هجمات DOM XSS والتي يجب تغييرها.

حلّ المشاكل في الانتهاكات

هناك خياران لحلّ انتهاكات الأنواع الموثوق بها. يمكنك إزالة الرمز البرمجي المسيء، استخدام مكتبة، إنشاء سياسة أنواع موثوق بها، أو إنشاء سياسة تلقائية كحل أخير.

إعادة كتابة الرمز البرمجي المعنيّ

من المحتمل أنّه لم يعُد هناك حاجة إلى الرمز غير المتوافق، أو يمكن إعادة كتابته بدون الدوالّ التي تتسبّب في الانتهاكات:

الإجراءات الموصى بها
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
الإجراءات غير المُوصى بها
el.innerHTML = '<img src=xyz.jpg>';

استخدام مكتبة

تُنشئ بعض المكتبات أنواعًا موثوق بها يمكنك تمريرها إلى وظائف sink. على سبيل المثال، يمكنك استخدام DOMPurify لتطهير مقتطف HTML، وإزالة حِزم بيانات XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

توفّر خدمة DOMPurify الأنواع الموثوق بها وتعرض محتوى HTML مصحّحًا ملفوفًا في كائن TrustedHTML حتى لا ينشئ المتصفح انتهاكًا.

إنشاء سياسة أنواع موثوق بها

في بعض الأحيان، لا يمكنك إزالة الرمز البرمجي الذي يتسبب في الانتهاك، ولا تتوفّر مكتبة لتنظيف القيمة وإنشاء نوع موثوق لك. في هذه الحالات، يمكنك إنشاء كائن من النوع "موثوق به" بنفسك.

أولاً، أنشئ سياسة. السياسات هي مصانع لـ Trusted Types التي تفرض قواعد أمان معيّنة على مدخلاتها:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

تنشئ هذه التعليمة البرمجية سياسة باسم myEscapePolicy يمكنها إنشاء TrustedHTML كائنات باستخدام دالة createHTML(). تعمل القواعد المحدّدة على ترميز أحرف < بترميز HTML لمنع إنشاء عناصر HTML جديدة.

استخدِم السياسة على النحو التالي:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

استخدام سياسة تلقائية

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

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

يتم استخدام السياسة المُسماة default عند استخدام سلسلة في حوض لا يقبل سوى النوع الموثوق به.

التبديل إلى فرض سياسة أمان المحتوى

عندما يتوقّف تطبيقك عن حدوث انتهاكات، يمكنك بدء فرض الأنواع الموثوق بها:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

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

مراجع إضافية