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