النصوص البرمجية للمواقع الإلكترونية (XSS)، وهي إمكانية حقن نصوص برمجية ضارة في تطبيق ويب، كانت لعدة عقود واحدة من أكبر الثغرات الأمنية في أمان الويب.
سياسة أمان المحتوى (CSP)
هي طبقة أمان إضافية تساعد في الحدّ من هجمات XSS. لإعداد سياسة CSP،
أضِف عنوان HTTP Content-Security-Policy
إلى صفحة ويب واضبط القيم التي تتحكم في الموارد التي يمكن لوكيل المستخدم تحميلها لهذه الصفحة.
توضّح هذه الصفحة كيفية استخدام سياسة أمان المحتوى المستندة إلى الرموز العشوائية أو التجزئات للحدّ من الهجمات التي تستخدم النصوص البرمجية على المواقع الإلكترونية (XSS)، بدلاً من سياسات أمان المحتوى المستندة إلى القائمة المسموح بها للمضيفين والتي غالبًا ما تترك الصفحة معرّضة للهجوم لأنّه يمكن تجاوزها في معظم عمليات الضبط.
المصطلح الرئيسي: المفتاح المؤقت هو رقم عشوائي يُستخدَم مرة واحدة فقط، ويمكنك استخدامه لتمييز علامة
<script>
على أنّها موثوق بها.
المصطلح الرئيسي: دالة التجزئة هي دالة رياضية تحوّل قيمة إدخال
إلى قيمة رقمية مضغوطة تُسمى تجزئة. يمكنك استخدام تجزئة
(مثل SHA-256) لوضع علامة على
علامة <script>
المضمَّنة كموثوق بها.
يُطلق على سياسة أمان المحتوى المستندة إلى الرموز العشوائية أو التجزئات عادةً اسم سياسة أمان المحتوى الصارمة. عندما يستخدم تطبيق سياسة CSP صارمة، لن يتمكن المهاجمون الذين يجدون أخطاء حقن HTML بشكل عام من استخدامها لإجبار المتصفح على تنفيذ نصوص برمجية ضارة في مستند به ثغرة أمنية. ويرجع ذلك إلى أنّ سياسة CSP الصارمة لا تسمح إلا بالنصوص البرمجية المجزّأة أو النصوص البرمجية التي تحتوي على قيمة nonce الصحيحة التي تم إنشاؤها على الخادم، لذلك لا يمكن للمهاجمين تنفيذ هذا النص البرمجي بدون معرفة القيمة الصحيحة لاستجابة معيّنة.
لماذا عليك استخدام سياسة CSP صارمة؟
إذا كان موقعك الإلكتروني يتضمّن حاليًا خدمة CSP تشبه script-src www.googleapis.com
،
من المحتمل أنّها غير فعّالة في مواجهة الهجمات على مستوى المواقع الإلكترونية. ويُسمى هذا النوع من سياسة أمان المحتوى (CSP)
القائمة المسموح بها CSP. وتتطلّب هذه الحلول الكثير من التخصيص ويمكن للمهاجمين
تجاوزها.
يمكن لـ CSPs الصارم استنادًا إلى أرقام التشفير أو أجزائها تجنُّب هذه المخاطر.
بنية سياسة أمان المحتوى (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';
تجعل السمات التالية خدمة إدارة مفاتيح المحتوى (CSP) مثل هذه "صارمة" وبالتالي آمنة:
- ويستخدم هذا الإجراء أرقامًا عشوائية
'nonce-{RANDOM}'
أو رموزًا هاشتاغ'sha256-{HASHED_INLINE_SCRIPT}'
للإشارة إلى علامات<script>
التي يثق بها مطوّر الموقع الإلكتروني لتنفيذها في متصفّح المستخدم. - تعمل هذه السياسة على ضبط
'strict-dynamic'
على الحدّ من الجهد المبذول لنشر سياسة أمان محتوى (CSP) لا تعتمد على التجزئة أو علامة تجزئة، وذلك من خلال السماح تلقائيًا بتنفيذ النصوص البرمجية التي ينشئها نص برمجي موثوق. ويؤدي ذلك أيضًا إلى إزالة حظر استخدام معظم مكتبات JavaScript وتطبيقات المصغّرات التابعة لجهات خارجية. - ولا يستند إلى قوائم المسموح بها لعناوين URL، لذا لا يواجه عمليات التحايل الشائعة على خدمة CSP.
- تحظر هذه السياسة النصوص البرمجية المضمّنة غير الموثوق بها، مثل معالِجات الأحداث المضمّنة أو معرّفات الموارد المنتظِمة
javascript:
. - يفرض هذا الإعداد قيودًا على
object-src
لإيقاف الإضافات الخطيرة، مثل Flash. - يفرض هذا الإجراء قيودًا على
base-uri
لحظر حقن علامات<base>
. ويمنع ذلك المهاجمين من تغيير مواقع النصوص البرمجية المحمَّلة من عناوين URL نسبية.
استخدام سياسة CSP صارمة
لاعتماد سياسة CSP صارمة، عليك إجراء ما يلي:
- حدِّد ما إذا كان يجب على تطبيقك ضبط سياسة CSP مستندة إلى مفتاح عشوائي أو تجزئة.
- انسخ سياسة CSP من قسم بنية سياسة CSP الصارمة واضبطها كعنوان استجابة في تطبيقك.
- أعد ضبط نماذج HTML والرمز من جهة العميل لإزالة الأنماط غير المتوافقة مع CSP.
- فعِّل سياسة أمان المحتوى (CSP).
يمكنك استخدام أفضل الممارسات في Lighthouse (الإصدار 7.3.0 والإصدارات الأحدث التي تتضمّن العلامة --preset=experimental
)
أثناء هذه العملية للتحقّق مما إذا كان موقعك الإلكتروني يستخدم بروتوكول CSP وما إذا كان
صارمًا بما يكفي ليكون فعّالاً في مكافحة هجمات XSS.
الخطوة 1: تحديد ما إذا كنت بحاجة إلى سياسة CSP مستندة إلى مفتاح عشوائي أو تجزئة
إليك طريقة عمل نوعَي سياسة CSP الصارمة:
سياسة أمان المحتوى المستندة إلى الرمز المميّز لمرة واحدة
باستخدام سياسة CSP المستندة إلى مفتاح تشفير عشوائي، يمكنك إنشاء رقم عشوائي أثناء التشغيل وتضمينه في سياسة CSP وربطه بكل علامة نص برمجي في صفحتك. لا يمكن للمخترِق تضمين نص برمجي ضارّ أو تشغيله في صفحتك، لأنّه يحتاج إلى تخمين الرقم العشوائي الصحيح لهذا النص البرمجي. لا تعمل هذه الميزة إلا إذا كان الرقم غير قابل للتخمين، ويتم إنشاؤه حديثًا في وقت التشغيل لكل استجابة.
استخدام سياسة CSP لا تستند إلى أي منصة مع صفحات HTML المعروضة على الخادم بالنسبة لهذه الصفحات، يمكنك إنشاء رقم عشوائي جديد لكل إجابة.
سياسة أمان المحتوى (CSP) المستندة إلى التجزئة
بالنسبة إلى سياسة CSP المستندة إلى التجزئة، تتم إضافة تجزئة كل علامة نص برمجي مضمّن إلى سياسة CSP. يحتوي كل نص برمجي على تجزئة مختلفة. لا يمكن للمهاجم تضمين ملف برمجي ضار أو تشغيله في صفحتك، لأنّه يجب أن تكون التجزئة لهذا الملف البرمجي في ملف CSP لكي يتم تشغيله.
استخدِم سياسة CSP المستندة إلى التجزئة لصفحات HTML التي يتم عرضها بشكل ثابت أو الصفحات التي يجب تخزينها مؤقتًا. على سبيل المثال، يمكنك استخدام بروتوكول CSP المستنِد إلى التجزئة لتطبيقات الويب المكوّنة من صفحة واحدة والتي تم إنشاؤها باستخدام إطارات عمل مثل Angular أو React أو غيرها، والتي يتم عرضها بشكلٍ ثابت بدون عرض من جهة الخادم.
الخطوة 2: ضبط سياسة CSP صارمة وإعداد النصوص البرمجية
عند إعداد سياسة أمان (CSP)، تتوفر لك بعض الخيارات:
- وضع "إعداد التقارير فقط" (
Content-Security-Policy-Report-Only
) أو وضع التنفيذ (Content-Security-Policy
): في وضع "إعداد التقارير فقط"، لن تحظر خدمة "إدارة خدمات المحتوى" موارد الويب بعد، وبالتالي لن يتعطّل أي محتوى على موقعك الإلكتروني، ولكن يمكنك الاطّلاع على الأخطاء والحصول على تقارير عن أي محتوى كان سيتم حظره. ولا يهمّ ذلك على المستوى المحلي عند ضبط سياسة CSP، لأنّ كلا الوضعين يعرضان الأخطاء التي تظهر في وحدة تحكم المتصفّح. على أي حال، يمكن أن يساعدك وضع التنفيذ في العثور على موارد تحظرها مسودة سياسة خدمة المحتوى (CSP)، لأنّ حظر أحد الموارد يمكن أن يجعل صفحتك تبدو متعطّلة. يصبح وضع "التقارير فقط" مفيدًا للغاية في وقت لاحق من العملية (راجِع الخطوة 5). - عنوان أو علامة
<meta>
في HTML بالنسبة إلى التطوير المحلي، يمكن أن تكون علامة<meta>
أكثر ملاءمةً لتعديل سياسة أمان المحتوى (CSP) ومعرفة مدى تأثيرها على موقعك الإلكتروني بسرعة. ومع ذلك:- لاحقًا، عند نشر سياسة CSP في مرحلة الإنتاج، ننصحك بضبطها كعنوان HTTP.
- إذا كنت تريد ضبط CSP في وضع "التقارير فقط"، عليك ضبطه كعنوان، لأنّ علامات CSP الوصفية لا تتيح وضع "التقارير فقط".
اضبط عنوان Content-Security-Policy
استجابة HTTP التالي
في تطبيقك:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
إنشاء رقم تعريف عشوائي لبروتوكول CSP
الرقم الخاص هو رقم عشوائي يتم استخدامه مرة واحدة فقط لكل عملية تحميل للصفحة. لا يمكن لميزة CSP المستندة إلى مفتاح عشوائي تخفيف هجمات XSS إلا إذا لم يتمكّن المهاجمون من تخمين قيمة المفتاح العشوائي. يجب أن يستوفي مفتاح nonce في بروتوكول CSP الشروط التالية:
- قيمة عشوائية قوية من الناحية التشفيرية (يُفضَّل أن يكون طولها 128 بت أو أكثر)
- يتم إنشاؤها حديثًا لكل ردّ
- مشفَّر بترميز Base64
في ما يلي بعض الأمثلة حول كيفية إضافة رقم CSP الخاص بأُطر العمل من جهة الخادم:
- 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. يمكن أن تحتوي جميع النصوص البرمجية على القيمة نفسها. الخطوة الأولى هي إضافة هذه السمات إلى جميع النصوص البرمجية حتى تسمح بها سياسة CSP.
اضبط عنوان Content-Security-Policy
استجابة HTTP التالي
في تطبيقك:
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>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
اعتبارات متعلقة بتحميل النصوص البرمجية
يضيف مثال النص البرمجي المضمّن s.async = false
لضمان
تنفيذ foo
قبل bar
، حتى إذا
تم تحميل bar
أولاً. في المقتطف التالي، لا تحظر s.async = false
المعالج أثناء تحميل النصوص البرمجية، لأنّ النصوص البرمجية تتم
إضافتها ديناميكيًا. يتوقف المحلل اللغوي فقط أثناء تنفيذ النصوص البرمجية،
كما هو الحال مع النصوص البرمجية لـ async
. ومع ذلك، عند استخدام هذا المقتطف،
يُرجى مراعاة ما يلي:
-
قد يتم تنفيذ نص برمجي واحد أو كليهما قبل انتهاء تنزيل المستند. إذا أردت أن يكون المستند جاهزًا عند تنفيذ
النصوص البرمجية، انتظِر حتى يتم حدث
DOMContentLoaded
قبل إلحاق النصوص البرمجية. إذا تسبّب ذلك في حدوث مشكلة في الأداء بسبب عدم بدء تنزيل النصوص البرمجية مبكرًا بما فيه الكفاية، استخدِم التحميل المُسبق للعلامات في موضع سابق على الصفحة. -
لا يؤدي
defer = true
أيّ وظيفة. إذا كنت بحاجة إلى هذا السلوك، يمكنك تشغيل النص البرمجي يدويًا عند الحاجة.
الخطوة 3: إعادة صياغة نماذج HTML والرمز البرمجي من جهة العميل
يمكن استخدام معالجات الأحداث المضمّنة (مثل onclick="…"
وonerror="…"
) وعناوين URL لبرامج JavaScript
(<a href="javascript:…">
) لتشغيل النصوص البرمجية. وهذا يعني أن المهاجم الذي يعثر على خطأ XSS يمكنه إدخال هذا النوع من محتوى HTML وتنفيذ لغة JavaScript ضارة. تحظر سياسة CSP المستندة إلى مفتاح عشوائي أو تجزئة استخدام هذا النوع من الترميز.
إذا كان موقعك يستخدم أيًا من هذه الأنماط، ستحتاج إلى إعادة استخدامها لإنشاء بدائل أكثر أمانًا.
إذا فعّلت أمان المحتوى في مرحلة الإعداد السابقة، ستتمكّن من الاطّلاع على انتهاكات أمان المحتوى في وحدة التحكّم في كل مرة يحظر فيها أمان المحتوى نمطًا غير متوافق.
في معظم الحالات، يكون الحلّ بسيطًا:
إعادة صياغة معالِجات الأحداث المضمّنة
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>
<span onclick="doThings();">A thing.</span>
إعادة صياغة معرّفات الموارد المنتظمة (javascript:
URI)
<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'
CSP، ما يجعل سياسة أمان المحتوى أقل أمانًا قليلاً.
يمكنك العثور على هذه الأمثلة وغيرها من أمثلة إعادة التنظيم هذه في الدرس التطبيقي التالي حول البرمجة المشفّرة للمحتوى (CSP):
الخطوة 4 (اختيارية): إضافة عناصر احتياطية لتتوافق مع إصدارات المتصفّح القديمة
إذا كنت بحاجة إلى دعم إصدارات المتصفّح القديمة:
- يتطلب استخدام
strict-dynamic
إضافةhttps:
كخيار احتياطي لإصدارات Safari السابقة. وعند إجراء ذلك:- تتجاهل جميع المتصفّحات التي تتيح
strict-dynamic
البديلhttps:
، لذلك لن يؤدي ذلك إلى تقليل قوة السياسة. - في المتصفّحات القديمة، لا يمكن تحميل النصوص البرمجية من مصادر خارجية إلا إذا كانت تأتي من
مصدر HTTPS. وهذا الإجراء أقل أمانًا من سياسة CSP الصارمة، ولكنه مع ذلك
يمنع بعض الأسباب الشائعة لهجوم XSS، مثل عمليات إدخال عناوين URL الخاصة بـ
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: نشر سياسة أمان المحتوى (CSP)
بعد التأكّد من أنّ سياسة CSP لا تحظر أي نصوص برمجية شرعية في بيئة التطوير المحلية، يمكنك نشر سياسة CSP في مرحلة الإنتاج، ثم إلى بيئة الإنتاج:
- (اختياري) يمكنك نشر سياسة أمان المحتوى (CSP) في وضع إعداد التقارير فقط باستخدام
عنوان
Content-Security-Policy-Report-Only
. يُعدّ وضع "إعداد التقارير فقط" مفيدًا لاختبار تغيير قد يؤدي إلى أعطال في عملية الإنتاج، مثل سياسة CSP جديدة في مرحلة الإنتاج، وذلك قبل بدء فرض قيود سياسة CSP. في وضع "إعداد التقارير فقط"، لا يؤثر ملفّ CSP في سلوك تطبيقك، ولكن يستمر المتصفّح في إنشاء أخطاء وحدة التحكّم وتقارير الانتهاكات عند رصد أنماط غير متوافقة مع ملفّ CSP، لكي تتمكّن من معرفة المشاكل التي كانت ستظهر للمستخدمين النهائيين. لمزيد من المعلومات، يُرجى الاطّلاع على Reporting API. - عندما تكون واثقًا من أنّ سياسة CSP لن تؤدي إلى إيقاف موقعك الإلكتروني للمستخدمين النهائيين،
انشر سياسة CSP باستخدام عنوان الاستجابة
Content-Security-Policy
. وننصحك بضبط CSP باستخدام عنوان HTTP من جهة الخادم لأنّه أكثر أمانًا من علامة<meta>
. بعد إكمال هذه الخطوة، يبدأ تنسيق CSP في حماية تطبيقك من هجمات XSS.
القيود
بشكل عام، إنّ سياسة CSP الصارمة توفّر طبقة أمان إضافية قوية تساعد على الحدّ من استخدام بروتوكول XSS. وفي معظم الحالات، يقلل سياسة CSP من فرص الاختراق بشكل كبير، وذلك من خلال رفض الأنماط الخطيرة، مثل معرّفات الموارد المنتظمة (URI) javascript:
. ومع ذلك، استنادًا إلى نوع
سياسة CSP التي تستخدمها (الأرقام الخاصة أو التجزئات، مع 'strict-dynamic'
أو بدونه)، هناك
حالات لا تحمي فيها سياسة CSP تطبيقك أيضًا:
- إذا أضفت قيمة nonce إلى نص برمجي، ولكن تمّت حقن قيمة مباشرةً في النص أو في مَعلمة
src
لعنصر<script>
هذا. - إذا كانت هناك عمليات حقن في مواضع النصوص البرمجية التي تم إنشاؤها ديناميكيًا
(
document.createElement('script')
)، بما في ذلك أيّ دوال مكتبة تُنشئscript
عقد DOM استنادًا إلى قيم وسيطاتها ويشمل ذلك بعض واجهات برمجة التطبيقات الشائعة مثل.html()
في jQuery، فضلاً عن.get()
و.post()
في jQuery < 3.0. - ما إذا كانت هناك عمليات حقن النماذج في تطبيقات AngularJS القديمة. ويمكن للمهاجم الذي يمكنه إدخال نموذج AngularJS استخدامه لتنفيذ JavaScript عشوائي.
- إذا تضمّنت السياسة
'unsafe-eval'
، يتم إدخال إدخالات فيeval()
وsetTimeout()
وبعض واجهات برمجة التطبيقات الأخرى التي نادرًا ما يتم استخدامها.
على المطوّرين ومهندسي الأمان الانتباه بشكل خاص إلى هذه النماذج أثناء مراجعات الرموز البرمجية وعمليات تدقيق الأمان. يمكنك العثور على مزيد من التفاصيل حول هذه الحالات في المقالة Content Security Policy: A Successful Mess Between Hardening and Mitigation.