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

Krzysztof Kotowicz
Krzysztof Kotowicz

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

  • Chrome: 83
  • ‫Edge: 83
  • Firefox: غير متوافق
  • Safari: غير متوافق

المصدر

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

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

الخلفية

على مدار سنوات عديدة، كانت الاختراقات من خلال استغلال ثغرة XSS في نموذج DOM من أكثر نقاط ضعف أمان الويب شيوعًا وخطورة.

هناك نوعان من هجمات النصوص البرمجية عبر المواقع الإلكترونية. تحدث بعض نقاط ضعف 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، ويمكنك حظره بشكل أكبر من خلال حصر إنشاء السياسات.

مراجع إضافية