Browser Support
هجمة النصوص البرمجية على المواقع الإلكترونية (XSS)، وهي القدرة على حقن نصوص برمجية ضارة في تطبيق ويب، كانت واحدة من أكبر الثغرات الأمنية على الويب لأكثر من عقد من الزمان.
سياسة أمان المحتوى (CSP) هي طبقة أمان إضافية تساعد في الحدّ من هجمات XSS. لضبط سياسة أمان المحتوى،
أضِف عنوان HTTP Content-Security-Policy إلى صفحة ويب واضبط القيم التي
تتحكّم في الموارد التي يمكن لوكيل المستخدم تحميلها لتلك الصفحة.
توضّح هذه الصفحة كيفية استخدام سياسة أمان محتوى تستند إلى أرقام عشوائية أو تجزئات للحدّ من هجمات XSS، بدلاً من سياسات أمان المحتوى شائعة الاستخدام التي تستند إلى القائمة المسموح بها للمضيفين والتي غالبًا ما تترك الصفحة عرضةً لهجمات XSS لأنّه يمكن تجاوزها في معظم عمليات الضبط.
مصطلح أساسي: الرقم الخاص هو رقم عشوائي يُستخدم مرة واحدة فقط ويمكنك استخدامه لوضع علامة على علامة <script> على أنّها موثوقة.
مصطلح أساسي: دالة التجزئة هي دالة رياضية تحوّل قيمة الإدخال إلى قيمة رقمية مضغوطة تُسمى تجزئة. يمكنك استخدام تجزئة (مثل SHA-256) لوضع علامة على علامة <script> مضمّنة على أنّها موثوقة.
غالبًا ما يُطلق على "سياسة أمان المحتوى" المستندة إلى أرقام عشوائية أو تجزئات اسم سياسة أمان المحتوى الصارمة. عندما يستخدم أحد التطبيقات سياسة أمان محتوى صارمة، لا يمكن للمهاجمين الذين يعثرون على عيوب في حقن HTML استخدامها لإجبار المتصفح على تنفيذ نصوص برمجية ضارة في مستند ضعيف. ويعود السبب في ذلك إلى أنّ سياسة أمان المحتوى الصارمة تسمح فقط بالنصوص البرمجية المجزّأة أو النصوص البرمجية التي تتضمّن قيمة nonce الصحيحة التي تم إنشاؤها على الخادم، وبالتالي لا يمكن للمهاجمين تنفيذ النص البرمجي بدون معرفة قيمة nonce الصحيحة لاستجابة معيّنة.
لماذا يجب استخدام سياسة أمان محتوى صارمة؟
إذا كان موقعك الإلكتروني يتضمّن سياسة أمان محتوى (CSP) تشبه script-src www.googleapis.com،
من المحتمل أنّها غير فعّالة ضد الهجمات عبر المواقع الإلكترونية. يُطلق على هذا النوع من "سياسة أمان المحتوى" اسم
"سياسة أمان المحتوى" المستندة إلى القائمة المسموح بها. وتتطلّب الكثير من التخصيص ويمكن للمهاجمين تجاوزها.
وتتجنّب سياسات CSP الصارمة المستندة إلى رموز تشفير عشوائية أو تجزئات هذه المشاكل.
بنية سياسة أمان المحتوى (CSP) الصارمة
تستخدم "سياسة أمان المحتوى" الصارمة الأساسية أحد عناوين استجابة HTTP التالية:
"سياسة أمان المحتوى" الصارمة المستندة إلى الرموز المميّزة
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
سياسة أمان المحتوى الصارمة المستندة إلى التجزئة
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
تجعل السمات التالية سياسة أمان المحتوى مثل هذه السياسة "صارمة" وبالتالي آمنة:
- تستخدم هذه السياسة أرقامًا تُستخدم مرة واحدة
'nonce-{RANDOM}'أو تجزئات'sha256-{HASHED_INLINE_SCRIPT}'للإشارة إلى علامات<script>التي يثق مطوّر الموقع الإلكتروني في تنفيذها في متصفّح المستخدم. - وتضبط هذه السياسة
'strict-dynamic'لتقليل الجهد المبذول في نشر سياسة CSP المستندة إلى nonce أو التجزئة من خلال السماح تلقائيًا بتنفيذ النصوص البرمجية التي ينشئها نص برمجي موثوق به. ويؤدي ذلك أيضًا إلى إزالة حظر استخدام معظم مكتبات JavaScript وعناصر واجهة المستخدم التابعة لجهات خارجية. - ولا يستند إلى القوائم المسموح بها لعناوين URL، لذا لا يتأثر بعمليات تجاوز سياسة أمان المحتوى الشائعة.
- وتحظر النصوص البرمجية المضمّنة غير الموثوق بها، مثل معالِجات الأحداث المضمّنة أو معرّفات الموارد المنتظمة (URI) من النوع
javascript:. - يمنع هذا الإعداد "
object-src" من إيقاف المكوّنات الإضافية الخطيرة، مثل Flash. - ويقيّد
base-uriلحظر إدخال علامات<base>. يمنع ذلك المهاجمين من تغيير مواقع النصوص البرمجية التي يتم تحميلها من عناوين URL نسبية.
اعتماد سياسة أمان محتوى صارمة
لاعتماد سياسة أمان محتوى صارمة، عليك إجراء ما يلي:
- حدِّد ما إذا كان يجب أن يضبط تطبيقك سياسة CSP مستندة إلى nonce أو إلى التجزئة.
- انسخ "سياسة أمان المحتوى" من قسم بنية سياسة أمان المحتوى الصارمة واضبطها كعنوان استجابة في تطبيقك.
- أعِد صياغة نماذج HTML والرمز البرمجي من جهة العميل لإزالة الأنماط غير المتوافقة مع سياسة أمان المحتوى.
- انشر "سياسة أمان المحتوى".
يمكنك استخدام تدقيق أفضل الممارسات في Lighthouse (الإصدار 7.3.0 والإصدارات الأحدث مع العلامة --preset=experimental) طوال هذه العملية للتحقّق مما إذا كان موقعك الإلكتروني يتضمّن سياسة أمان محتوى (CSP) وما إذا كانت صارمة بما يكفي لتكون فعّالة ضد هجمات XSS.
الخطوة 1: تحديد ما إذا كنت بحاجة إلى سياسة CSP مستندة إلى قيمة عشوائية أو تجزئة
في ما يلي كيفية عمل نوعَي "سياسة أمان المحتوى" الصارمة:
"سياسة أمان المحتوى" المستندة إلى الرموز المميّزة
باستخدام "سياسة أمان المحتوى" المستندة إلى رقم خاص، يمكنك إنشاء رقم عشوائي في وقت التشغيل، وتضمينه في "سياسة أمان المحتوى"، وربطه بكل علامة برمجية في صفحتك. لا يمكن للمهاجم تضمين نص برمجي ضار أو تشغيله في صفحتك، لأنه سيحتاج إلى تخمين الرقم العشوائي الصحيح لهذا النص البرمجي. لا ينجح ذلك إلا إذا كان الرقم غير قابل للتخمين، ويتم إنشاؤه حديثًا في وقت التشغيل لكل استجابة.
استخدِم سياسة CSP مستندة إلى رقم عشوائي لصفحات HTML المعروضة على الخادم. بالنسبة إلى هذه الصفحات، يمكنك إنشاء رقم عشوائي جديد لكل ردّ.
سياسة أمان المحتوى المستندة إلى التجزئة
بالنسبة إلى "سياسة أمان المحتوى" المستندة إلى التجزئة، تتم إضافة تجزئة كل علامة برمجية مضمّنة إلى "سياسة أمان المحتوى". ويكون لكل نص برمجي تجزئة مختلفة. لا يمكن للمهاجم تضمين أو تشغيل نص برمجي ضار في صفحتك، لأنّ تجزئة هذا النص البرمجي يجب أن تكون في سياسة أمان المحتوى (CSP) ليتم تشغيله.
استخدِم سياسة CSP مستندة إلى التجزئة لصفحات HTML التي يتم عرضها بشكل ثابت أو الصفحات التي يجب تخزينها مؤقتًا. على سبيل المثال، يمكنك استخدام سياسة أمان محتوى مستندة إلى التجزئة لتطبيقات الويب ذات الصفحة الواحدة التي تم إنشاؤها باستخدام أُطر عمل مثل Angular أو React أو غيرها، والتي يتم عرضها بشكل ثابت بدون عرض من جهة الخادم.
الخطوة 2: ضبط سياسة أمان محتوى صارمة وإعداد النصوص البرمجية
عند ضبط سياسة CSP، تتوفّر لك بعض الخيارات:
- وضع "إعداد التقارير فقط" (
Content-Security-Policy-Report-Only) أو وضع "التنفيذ" (Content-Security-Policy): في وضع "إعداد التقارير فقط"، لن تحظر سياسة أمان المحتوى الموارد بعد، وبالتالي لن يتعطّل أي محتوى على موقعك الإلكتروني، ولكن يمكنك الاطّلاع على الأخطاء والحصول على تقارير عن أي محتوى كان سيتم حظره. محليًا، عند إعداد CSP، لا يهم ذلك حقًا، لأن كلا الوضعين يعرضان لك الأخطاء في وحدة تحكم المتصفح. على أي حال، يمكن أن يساعدك وضع التنفيذ في العثور على الموارد التي تحظرها مسودة "سياسة أمان المحتوى"، لأنّ حظر أحد الموارد قد يؤدي إلى ظهور صفحتك بشكل معطَّل. يصبح وضع "إعداد التقارير فقط" مفيدًا أكثر في مرحلة لاحقة من العملية (يُرجى الاطّلاع على الخطوة 5). - العنوان أو علامة HTML
<meta>بالنسبة إلى التطوير المحلي، يمكن أن تكون علامة<meta>أكثر ملاءمة لتعديل سياسة أمان المحتوى ومعرفة مدى تأثيرها في موقعك الإلكتروني بسرعة. تجدر الإشارة إلى ما يلي:- لاحقًا، عند نشر سياسة CSP في مرحلة الإنتاج، ننصح بضبطها كعنوان HTTP.
- إذا أردت ضبط سياسة أمان المحتوى في وضع إعداد التقارير فقط، عليك ضبطها كعنوان، لأنّ العلامات الوصفية لسياسة أمان المحتوى لا تتيح وضع إعداد التقارير فقط.
اضبط عنوان استجابة HTTP التالي في تطبيقك: Content-Security-Policy
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
إنشاء رقم خاص لسياسة أمان المحتوى
الرقم الخاص هو رقم عشوائي يُستخدم مرة واحدة فقط لكل تحميل للصفحة. لا يمكن لسياسة أمان المحتوى (CSP) المستندة إلى رقم خاص أن تخفّف من هجمات XSS إلا إذا لم يتمكّن المهاجمون من تخمين قيمة الرقم الخاص. يجب أن يكون رقم CSP الخاص لمرة واحدة:
- قيمة عشوائية قوية من الناحية التشفيرية (يُفضَّل أن يكون طولها 128 بت أو أكثر)
- يتم إنشاؤه من جديد لكل ردّ
- بترميز Base64
في ما يلي بعض الأمثلة على كيفية إضافة رقم خاص في سياسة أمان المحتوى في أُطر العمل من جهة الخادم:
- Django (python)
- Express (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
إضافة سمة nonce إلى عناصر <script>
في سياسة أمان المحتوى (CSP) المستندة إلى رقم خاص يُستخدم لمرة واحدة، يجب أن يتضمّن كل عنصر <script> سمة nonce تتطابق مع قيمة الرقم الخاص الذي يُستخدم لمرة واحدة والمحدّد في رأس CSP. ويمكن أن يكون لجميع النصوص البرمجية قيمة nonce نفسها. الخطوة الأولى هي إضافة هذه السمات إلى جميع النصوص البرمجية لكي
تسمح بها سياسة أمان المحتوى.
اضبط عنوان استجابة HTTP التالي في تطبيقك: Content-Security-Policy
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
بالنسبة إلى النصوص البرمجية المضمّنة المتعددة، تكون الصيغة على النحو التالي:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.
تحميل النصوص البرمجية المصدرية ديناميكيًا
يمكنك تحميل نصوص برمجية تابعة لجهات خارجية بشكل ديناميكي باستخدام نص برمجي مضمّن.
<script>
var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];
scripts.forEach(function(scriptUrl) {
var s = document.createElement('script');
s.src = scriptUrl;
s.async = false; // to preserve execution order
document.head.appendChild(s);
});
</script>{HASHED_INLINE_SCRIPT}. لتقليل عدد التجزئات، يمكنك دمج جميع النصوص البرمجية المضمّنة
في نص برمجي واحد. للاطّلاع على هذا الإجراء عمليًا، يمكنك الرجوع إلى هذا المثال والرمز الخاص به.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
integrity التي تتطابق مع مصدر مسموح به.
ملاحظات حول تحميل النصوص البرمجية
يضيف مثال النص البرمجي المضمّن s.async = false لضمان تنفيذ foo قبل bar، حتى إذا تم تحميل bar أولاً. In this snippet, s.async = false
doesn't block the parser while the scripts load, because the scripts are
added dynamically. يتوقف المحلل اللغوي فقط أثناء تنفيذ النصوص البرمجية، كما هو الحال مع النصوص البرمجية async. ومع ذلك، يُرجى مراعاة ما يلي بشأن هذا المقتطف:
-
قد يتم تنفيذ أحد النصَّين البرمجيَّين أو كليهما قبل انتهاء تنزيل المستند. إذا أردت أن يكون المستند جاهزًا بحلول وقت تنفيذ النصوص البرمجية، انتظِر الحدث
DOMContentLoadedقبل إلحاق النصوص البرمجية. إذا تسبّب ذلك في مشكلة في الأداء لأنّ النصوص البرمجية لا يبدأ تنزيلها في وقت مبكر بما يكفي، استخدِم علامات التحميل المُسبق في وقت مبكر على الصفحة. -
لا يؤدي "
defer = true" أي إجراء. إذا كنت بحاجة إلى هذا السلوك، يمكنك تشغيل النص البرمجي يدويًا عند الحاجة.
الخطوة 3: إعادة هيكلة نماذج HTML والرمز البرمجي من جهة العميل
يمكن استخدام معالجات الأحداث المضمّنة (مثل onclick="…" وonerror="…") ومعرّفات الموارد المنتظمة (URI) لـ JavaScript (<a href="javascript:…">) لتشغيل النصوص البرمجية. وهذا يعني أنّ المهاجم الذي يعثر على ثغرة XSS يمكنه حقن هذا النوع من HTML وتنفيذ JavaScript ضار. تحظر سياسة CSP المستندة إلى nonce أو التجزئة استخدام هذا النوع من الترميز.
إذا كان موقعك الإلكتروني يستخدم أيًا من هذه الأنماط، عليك إعادة هيكلتها لتصبح بدائل أكثر أمانًا.
إذا فعّلت سياسة CSP في الخطوة السابقة، ستتمكّن من الاطّلاع على انتهاكات سياسة CSP في وحدة التحكّم في كل مرة تحظر فيها سياسة CSP نمطًا غير متوافق.
في معظم الحالات، يكون الحلّ بسيطًا:
إعادة هيكلة معالِجات الأحداث المضمّنة
<span id="things">A thing.</span>
<script nonce="${nonce}">
document.getElementById('things').addEventListener('click', doThings);
</script><span onclick="doThings();">A thing.</span>
إعادة هيكلة معرّفَي موارد منتظمَين (javascript:)
<a id="foo">foo</a>
<script nonce="${nonce}">
document.getElementById('foo').addEventListener('click', linkClicked);
</script><a href="javascript:linkClicked()">foo</a>
إزالة eval() من JavaScript
إذا كان تطبيقك يستخدم eval() لتحويل سلاسل JSON إلى كائنات JS، عليك إعادة هيكلة هذه الحالات إلى JSON.parse()، وهو أيضًا أسرع.
إذا لم تتمكّن من إزالة جميع استخدامات eval()، سيظل بإمكانك ضبط سياسة أمان محتوى صارمة مستندة إلى رقم عشوائي، ولكن عليك استخدام الكلمة الرئيسية 'unsafe-eval' لسياسة أمان المحتوى، ما يجعل سياستك أقل أمانًا بعض الشيء.
يمكنك العثور على هذه الأمثلة وغيرها من أمثلة إعادة التصميم في الدرس التطبيقي حول الترميز الخاص بسياسة أمان المحتوى الصارمة:
الخطوة 4 (اختيارية): إضافة إجراءات احتياطية لإتاحة إصدارات المتصفّحات القديمة
Browser Support
إذا كنت بحاجة إلى إتاحة إصدارات المتصفّحات القديمة:
- يتطلب استخدام
strict-dynamicإضافةhttps:كإعداد احتياطي للإصدارات السابقة من Safari. عند إجراء ذلك:- تتجاهل جميع المتصفّحات التي تتيح استخدام
strict-dynamicالإجراء الاحتياطيhttps:، لذا لن يؤدي ذلك إلى تقليل فعالية السياسة. - في المتصفحات القديمة، لا يمكن تحميل النصوص البرمجية من مصادر خارجية إلا إذا كانت واردة من
مصدر HTTPS. هذا الإجراء أقل أمانًا من سياسة CSP الصارمة، ولكنه يمنع بعض الأسباب الشائعة لهجمات XSS، مثل حقن عناوين URI من النوع
javascript:.
- تتجاهل جميع المتصفّحات التي تتيح استخدام
- لضمان التوافق مع إصدارات المتصفّحات القديمة جدًا (التي تزيد عن 4 سنوات)، يمكنك إضافة
unsafe-inlineكحلّ احتياطي. تتجاهل جميع المتصفّحات الحديثةunsafe-inlineفي حال توفّر رقم خاص أو تجزئة لسياسة أمان المحتوى (CSP).
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
الخطوة 5: نشر سياسة أمان المحتوى
بعد التأكّد من أنّ سياسة أمان المحتوى لا تحظر أي نصوص برمجية مشروعة في بيئة التطوير المحلية، يمكنك نشر سياسة أمان المحتوى في بيئة الاختبار، ثم في بيئة التشغيل الفعلي:
- (اختياري) يمكنك نشر سياسة أمان المحتوى في وضع إعداد التقارير فقط باستخدام العنوان
Content-Security-Policy-Report-Only. يُعدّ وضع "إعداد التقارير فقط" مفيدًا لاختبار تغيير قد يؤدي إلى عطل، مثل سياسة أمان محتوى جديدة في مرحلة الإنتاج قبل البدء في فرض قيود سياسة أمان المحتوى. في وضع "إعداد التقارير فقط"، لا تؤثر سياسة أمان المحتوى في سلوك تطبيقك، ولكن يواصل المتصفّح إنشاء أخطاء في وحدة التحكّم وتقارير الانتهاكات عندما يواجه أنماطًا غير متوافقة مع سياسة أمان المحتوى، حتى تتمكّن من معرفة ما كان سيتعطّل لدى المستخدمين النهائيين. لمزيد من المعلومات، يُرجى الاطّلاع على Reporting API. - عندما تتأكّد من أنّ سياسة CSP لن تؤدي إلى تعطّل موقعك الإلكتروني لدى المستخدمين النهائيين،
يمكنك نشر سياسة CSP باستخدام عنوان الاستجابة
Content-Security-Policy. ننصحك بضبط سياسة CSP باستخدام عنوان HTTP من جهة الخادم لأنّه أكثر أمانًا من علامة<meta>. بعد إكمال هذه الخطوة، تبدأ سياسة أمان المحتوى في حماية تطبيقك من هجمات XSS.
القيود
توفّر سياسة أمان المحتوى الصارمة بشكل عام طبقة أمان إضافية قوية تساعد في
الحدّ من هجمات XSS. في معظم الحالات، تقلّل سياسة أمان المحتوى (CSP) من فرص الاختراق بشكل كبير، وذلك من خلال رفض الأنماط الخطيرة، مثل معرّفات الموارد المنتظمة (URI) التي تستخدم javascript:. ومع ذلك، استنادًا إلى نوع سياسة CSP التي تستخدمها (nonces أو hashes، مع أو بدون 'strict-dynamic')، هناك حالات لا تحمي فيها سياسة CSP تطبيقك أيضًا:
- إذا استخدمت رقمًا خاصًا في نص برمجي، ولكن تم إدخال رمز مباشرةً في نص أو مَعلمة
srcلعنصر<script>. - إذا كانت هناك عمليات حقن في مواقع النصوص البرمجية التي تم إنشاؤها ديناميكيًا
(
document.createElement('script'))، بما في ذلك أي دوال مكتبة تنشئ عُقدscriptDOM استنادًا إلى قيم وسيطاتها. ويشمل ذلك بعض واجهات برمجة التطبيقات الشائعة، مثل.html()في jQuery، بالإضافة إلى.get()و.post()في jQuery < 3.0. - إذا كانت هناك عمليات حقن للنماذج في تطبيقات AngularJS القديمة يمكن للمهاجم الذي يمكنه إدخال رمز في نموذج AngularJS استخدامه لتنفيذ رمز JavaScript عشوائي.
- إذا كانت السياسة تحتوي على
'unsafe-eval'، سيتم حظر عمليات الحقن فيeval()وsetTimeout()وبعض واجهات برمجة التطبيقات الأخرى التي نادرًا ما يتم استخدامها.
يجب أن يولي المطوّرون ومهندسو الأمان اهتمامًا خاصًا لهذه الأنماط أثناء مراجعات الرموز البرمجية وعمليات التدقيق الأمني. يمكنك العثور على مزيد من التفاصيل حول هذه الحالات في مقالة سياسة أمان المحتوى: فوضى ناجحة بين التعزيز والتخفيف.