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

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: behind a flag.
  • Safari: 26.

Source

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

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

الخلفية

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

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

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

يمكن للمتصفّحات الآن أيضًا المساعدة في منع هجمات XSS المستنِدة إلى DOM من جهة العميل باستخدام Trusted Types.

مقدمة عن واجهة برمجة التطبيقات

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

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

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

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

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

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

كيفية استخدام Trusted Types

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

يمكنك نشر أداة لجمع التقارير، مثل 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 التالي إلى المستندات التي تريد نقلها إلى Trusted Types:

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

من الآن فصاعدًا، في كل مرة ترصد فيها واجهة برمجة التطبيقات 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 والتي يجب تغييرها.

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

هناك خياران لإصلاح انتهاك Trusted Types. يمكنك إزالة الرمز البرمجي المخالف أو استخدام مكتبة أو إنشاء سياسة Trusted Type أو إنشاء سياسة تلقائية كحلّ أخير.

إعادة كتابة الرمز البرمجي المخالف

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

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

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

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

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

تتوافق مكتبة DOMPurify مع Trusted Types وتعرض ترميز HTML معقّمًا مضمّنًا في كائن TrustedHTML، ما يمنع المتصفّح من إنشاء انتهاك.

إنشاء سياسة Trusted Types

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

أولاً، أنشِئ سياسة. السياسات هي مصانع لـ 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 في أي مكان يتم فيه استخدام سلسلة في مصدر لا يقبل سوى Trusted Type.

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

عندما يتوقف تطبيقك عن تسجيل مخالفات، يمكنك البدء في فرض استخدام Trusted Types باتّباع الخطوات التالية:

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

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

محتوى إضافي للقراءة