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

Krzysztof Kotowicz
Krzysztof Kotowicz

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

  • 83
  • 83
  • x
  • x

المصدر

تحدث البرمجة النصية على المواقع الإلكترونية المستندة إلى نموذج العناصر في المستند (DOM XSS) (DOM XSS) عندما تصل بيانات من مصدر يتحكّم فيه المستخدم (مثل اسم المستخدم أو عنوان 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 في تطبيقك بشكل كبير. يبسط هذا التطبيق مراجعات الأمان، ويتيح لك فرض فحوصات الأمان المستندة إلى النوع والتي يتم إجراؤها عند تجميع الرمز البرمجي أو دمجه أو دمجه في وقت التشغيل في المتصفّح.

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

الاستعداد للإبلاغ عن انتهاك سياسة أمان المحتوى

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

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 = '';

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

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

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