تحدث هجمات البرمجة النصية على مواقع متعددة المستنِدة إلى نموذج العناصر في المستند (DOM XSS) عندما تصل بيانات من
مصدر يتحكّم فيه المستخدم (مثل اسم مستخدِم أو عنوان URL لإعادة التوجيه مأخوذ من جزء عنوان URL
) إلى مستقبِل، وهو دالة مثل eval() أو أداة ضبط للسمات
مثل .innerHTML يمكنها تنفيذ رمز JavaScript عشوائي.
تُعدّ هجمات DOM XSS من أكثر الثغرات الأمنية شيوعًا على الويب، ومن الشائع أن تُدخلها فرق التطوير عن غير قصد في تطبيقاتها. توفر لك واجهة برمجة التطبيقات Trusted Types الأدوات اللازمة لكتابة التطبيقات ومراجعتها من الناحية الأمنية والحفاظ عليها خالية من ثغرات DOM XSS من خلال تأمين دوال واجهة برمجة التطبيقات الخطيرة على الويب تلقائيًا. تتوفّر واجهة برمجة التطبيقات Trusted Types كإضافة polyfill للمتصفّحات التي لا تتيحها بعد.
خلفية
على مدار سنوات عديدة، كانت هجمات DOM XSS من أكثر الثغرات الأمنية شيوعًا وخطورةً على الويب.
هناك نوعان من هجمات البرمجة النصية على مواقع متعددة. تنتج بعض ثغرات XSS عن رمز من جهة الخادم ينشئ بشكل غير آمن رمز HTML الذي يشكّل الموقع الإلكتروني. ويكون السبب الأساسي لبعضها الآخر من جهة العميل، حيث يستدعي رمز JavaScript دوال خطيرة باستخدام محتوى يتحكّم فيه المستخدم.
لمنع هجمات XSS من جهة الخادم، لا تنشئ رمز HTML من خلال ربط السلاسل. استخدِم بدلاً من ذلك مكتبات قوالب آمنة لإلغاء الأحرف الخاصة تلقائيًا في السياق ، بالإضافة إلى "سياسة أمان المحتوى" المستندة إلى قيمة nonce للحدّ من الأخطاء بشكل إضافي.
يمكن للمتصفّحات الآن أيضًا المساعدة في منع هجمات DOM XSS من جهة العميل باستخدام واجهة برمجة التطبيقات Trusted Types.
مقدمة عن واجهة برمجة التطبيقات
تعمل واجهة برمجة التطبيقات Trusted Types من خلال تأمين دوال المستقبِل الخطيرة التالية. قد تكون بعضها مألوفة لديك، لأنّ مورّدي المتصفّحات و أُطر الويب يوجّهونك بالفعل إلى تجنُّب استخدام هذه الميزات لأسباب أمنية.
- تعديل النص البرمجي:
<script src>وضبط المحتوى النصي لعناصر<script> - إنشاء رمز HTML من سلسلة:
- تنفيذ محتوى المكوّن الإضافي:
- تجميع رمز JavaScript في وقت التشغيل:
evalsetTimeoutsetIntervalnew Function()
تتطلّب واجهة برمجة التطبيقات Trusted Types معالجة البيانات قبل تمريرها إلى دوال المستقبِل هذه. سيؤدي استخدام سلسلة فقط إلى حدوث خطأ، لأنّ المتصفّح لا يعرف ما إذا كانت البيانات موثوقة:
anElement.innerHTML = location.href;
للإشارة إلى أنّه تمت معالجة البيانات بشكل آمن، أنشئ كائنًا خاصًا، وهو نوع موثوق به.
anElement.innerHTML = aTrustedHTML;
TrustedHTML
للمستقبِلات التي تتوقّع مقتطفات HTML. هناك أيضًا
TrustedScript وTrustedScriptURL كائنات للمستقبِلات الحساسة الأخرى.
تقلّل واجهة برمجة التطبيقات Trusted Types بشكل كبير من سطح هجوم DOM XSS لتطبيقك. تُبسّط هذه الواجهة عمليات المراجعة الأمنية، وتتيح لك فرض عمليات التحقّق الأمنية المستندة إلى النوع التي يتم إجراؤها عند تجميع الرمز أو تنقيحه أو تجميع حِزمه في وقت التشغيل في المتصفّح.
كيفية استخدام واجهة برمجة التطبيقات Trusted Types
الاستعداد لتلقّي تقارير انتهاكات "سياسة أمان المحتوى"
يمكنك نشر أداة لجمع التقارير، مثل أداة معالجة Reporting API مفتوحة المصدر أو أداة 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 Type. يمكنك إزالة الرمز المخالف، استخدام مكتبة، إنشاء سياسة Trusted Type أو، كحلّ أخير، إنشاء سياسة تلقائية.
إعادة كتابة الرمز المخالف
من المحتمل ألا يكون الرمز غير المتوافق مطلوبًا بعد الآن، أو يمكن إعادة كتابته بدون الدوال التي تتسبب في الانتهاكات:
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 مع واجهة برمجة التطبيقات Trusted Types
وتعرض رمز HTML الذي تم تنظيفه والمغلّف في كائن TrustedHTML حتى لا يعرض المتصفّح
انتهاكًا.
إنشاء سياسة Trusted Type
في بعض الأحيان، لا يمكنك إزالة الرمز الذي يتسبب في الانتهاك، ولا تتوفّر أي مكتبة لتنظيف القيمة وإنشاء نوع موثوق به لك. في هذه الحالات، يمكنك إنشاء كائن Trusted Type بنفسك.
أولاً، أنشئ سياسة. السياسات هي أدوات إنشاء لأنواع موثوق بها تفرض قواعد أمان معيّنة على الإدخال:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
ينشئ هذا الرمز سياسة باسم myEscapePolicy يمكنها إنشاء كائنات TrustedHTML باستخدام الدالة createHTML(). تؤدي القواعد المحدّدة إلى إلغاء الأحرف الخاصة في HTML للعلامة < لمنع إنشاء عناصر HTML جديدة.
استخدِم السياسة على النحو التالي:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<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 Types:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
الآن، بغض النظر عن مدى تعقيد تطبيق الويب، فإنّ الشيء الوحيد الذي يمكن أن يؤدي إلى حدوث ثغرة أمنية لهجمات DOM XSS هو الرمز في إحدى سياساتك، ويمكنك تأمين ذلك بشكل أكبر من خلال الحدّ من إنشاء السياسات.