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

Krzysztof Kotowicz
Krzysztof Kotowicz

دعم المتصفح

  • 83
  • 83
  • x
  • x

المصدر

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

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

الخلفية

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

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

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

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

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

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

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

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

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

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

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

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

الاستعداد للإبلاغ عن مخالفة "سياسة أمان المحتوى"

يمكنك نشر أداة تجميع التقارير، مثل 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.

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

من الآن فصاعدًا، في كل مرة ترصد فيها ميزة "الأنواع الموثوق بها" انتهاكًا، يُرسِل المتصفّح تقريرًا إلى 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>';

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

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

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

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

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

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

أولاً، أنشئ سياسة. تُعد السياسات مصانع للأنواع الموثوق بها التي تفرض قواعد أمان معينة على البيانات التي يتم إدخالها:

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

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

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