تحدث هجمات "الاستهداف عبر المواقع" المستندة إلى نموذج DOM (DOM XSS) عندما تصل بيانات من مصدر يتحكم فيه المستخدم (مثل اسم مستخدم أو عنوان URL لإعادة التوجيه مأخوذ من جزء عنوان URL) إلى مستودع، وهو دالة مثل eval()
أو أداة ضبط خاصية مثل .innerHTML
يمكنها تنفيذ رمز JavaScript عشوائي.
DOM XSS هي إحدى أكثر الثغرات الأمنية شيوعًا على الويب، ومن الشائع أن تضعه فِرق التطوير عن طريق الخطأ في تطبيقاتهم. تمنحك الأنواع الموثوق بها الأدوات اللازمة لكتابة التطبيقات ومراجعتها من حيث الأمان والحفاظ عليها من ثغرات DOM XSS من خلال جعل وظائف واجهة برمجة التطبيقات الخطيرة آمنة بشكل تلقائي. وتتوفر "الأنواع الموثوق بها" على هيئة polyfill في المتصفحات التي لا تتوافق معها حتى الآن.
الخلفية
لسنوات عديدة، لطالما كان DOM XSS إحدى أكثر الثغرات الأمنية انتشارًا وخطورة على الويب.
هناك نوعان من هجمات النصوص البرمجية عبر المواقع الإلكترونية. تحدث بعض نقاط ضعف XSS بسبب رمز برمجي من جهة الخادم ينشئ رمز HTML الذي يشكّل الموقع الإلكتروني بشكل غير آمن. والبعض الآخر يكون له سبب أساسي على العميل، حيث يستدعي رمز JavaScript دوال خطيرة مع محتوى يتحكّم فيه المستخدم.
لـ منع هجمات XSS من جهة الخادم، لا تنشئ صفحات HTML من خلال تسلسل سلاسل. استخدِم مكتبات النماذج الآمنة التي تتضمّن ميزة "الإخفاء التلقائي للمحتوى في سياقه" بدلاً من ذلك، بالإضافة إلى سياسة أمان المحتوى المستندة إلى مفتاح عشوائي لتجنّب الأخطاء بشكلٍ أكبر.
يمكن للمتصفّحات الآن أيضًا المساعدة في منع هجمات XSS المستندة إلى نموذج العناصر في المستند (DOM) من جهة العميل باستخدام الأنواع الموثوق بها.
مقدمة إلى واجهة برمجة التطبيقات
تعمل Trusted Types من خلال قفل وظائف التصدير الخطيرة التالية. قد تكون على دراية ببعض هذه الميزات، لأنّ مورّدي المتصفّحات وأطر عمل الويب ينصحونك بعدم استخدامها لأسباب تتعلق بالأمان.
- معالجة النص البرمجي:
<script src>
وضبط محتوى نص العناصر<script>
. - إنشاء محتوى HTML من سلسلة:
- تنفيذ محتوى المكوّن الإضافي:
- تجميع رموز JavaScript البرمجية في وقت التشغيل:
eval
setTimeout
setInterval
new Function()
تتطلّب الأنواع الموثوق بها معالجة البيانات قبل تمريرها إلى وظائف sink هذه. لا يؤدي استخدام سلسلة فقط إلى تحقيق النجاح، لأنّ المتصفّح لا يعرف ما إذا كانت البيانات جديرة بالثقة:
anElement.innerHTML = location.href;
للإشارة إلى أنّه تمت معالجة البيانات بأمان، أنشئ عنصرًا خاصًا، وهو النوع الموثوق.
anElement.innerHTML = aTrustedHTML;
تقلِّل 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, '<')
});
}
تنشئ هذه التعليمة البرمجية سياسة باسم 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
عند استخدام سلسلة في حوض لا يقبل سوى النوع الموثوق به.
التبديل إلى فرض سياسة أمان المحتوى
عندما يتوقّف تطبيقك عن حدوث انتهاكات، يمكنك بدء فرض الأنواع الموثوق بها:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
الآن، بغض النظر عن مدى تعقيد تطبيق الويب، فإنّ الرمز البرمجي في إحدى سياساتك هو الشيء الوحيد الذي يمكنه إدخال ثغرة XSS في DOM، ويمكنك قفل هذا الرمز البرمجي بشكل أكبر من خلال تقييد إنشاء السياسات.