العودة إلى المدونة

سكربت التحليلات لديك هو الثغرة في سياسة أمان المحتوى الخاصة بك

سياسة CSP صارمة تُغلق ثغرات XSS. ووسم تحليلات تابع لطرف ثالث يعيد فتحها. إليك سبب تقويض قوائم النطاقات المسموح بها وغياب SRI لسياستك — وما الذي يصلحه متتبع أول الطرف.

تُعد سياسة أمان المحتوى (Content-Security-Policy) أكثر دفاع فعالية مفرد ضد البرمجة عبر المواقع (cross-site scripting). وفي اللحظة التي تضيف فيها وسم تحليلات تابعًا لطرف ثالث، تضطر عادةً إلى إحداث ثغرة فيها. وتلك الثغرة هي حيث تعيش معظم حالات تجاوز CSP في العالم الحقيقي.

السبب بنيوي وليس عرضيًا. فسكربتات التحليلات كثيفة المراقبة مصممة لتحميل المزيد من الشيفرة في وقت التشغيل، من نطاقات لا تتحكم فيها. وسياسة CSP الصارمة موجودة لتمنع ذلك بالضبط.

قوائم النطاقات المسموح بها هي الطريقة الضعيفة للوثوق بسكربت

الطريقة القديمة للسماح بمزوّد تحليلات كانت قائمة نطاقات مسموح بها (host allowlist):

Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://www.googletagmanager.com;

لم تعد OWASP توصي بهذا النمط. فقوائم النطاقات المسموح بها يسهل تجاوزها: أي أصل (origin) مسموح به يستضيف نقطة طرفية من نوع JSONP، أو إعادة توجيه مفتوحة، أو سكربتات رفعها المستخدمون، يصبح ناقلًا لهجمات XSS. ومديرو الوسوم (tag managers) يزيدون الأمر سوءًا، لأنهم موجودون تحديدًا لحقن المزيد من السكربتات من أصول عشوائية.

أما الممارسة الرائدة الحالية فهي سياسة CSP صارمة مبنية على nonce لكل استجابة بالإضافة إلى strict-dynamic:

Content-Security-Policy: script-src 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none';

مع strict-dynamic، يثق المتصفح بالسكربتات التي يحمّلها سكربت موثوق به بالفعل — وهذا بالضبط هو السلوك الذي يعتمد عليه مدير الوسوم. وهذا مريح للمزوّد وخطير عليك: فوسم واحد مخترَق يملك الآن إذنًا ضمنيًا لتحميل أي شيء.

SRI لا تعمل على السكربتات التي تحتاجها أكثر من غيرها

تتيح سلامة المورد الفرعي (Subresource Integrity) للمتصفح التحقق من ملف مجلوب مقابل تجزئة (hash) تشفيرية قبل تنفيذه:

<script
  src="https://cdn.example.com/tracker.js"
  integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
  crossorigin="anonymous"></script>

تدعم SRI خوارزميات sha256 وsha384 وsha512، ويرفض المتصفح المورد عند أي عدم تطابق. وهي دفاع نظيف ضد شبكة CDN مخترَقة — بالنسبة إلى الملفات الثابتة المثبّتة على إصدار محدد.

لكنها تنهار مع التحليلات. فأوسمة المزوّدين متغيرة عمدًا: الملف عند نفس عنوان URL يتغير كلما أطلق المزوّد ميزات، لذا فإن تجزئة مثبّتة ستعطّل عملية الجمع عند النشر التالي. وعمليًا تتخلى الفرق عن SRI تحديدًا على السكربتات ذات الوصول الأوسع داخل الصفحة. وقد صنّفت إعادة معايرة مخاطر سلسلة التوريد في أكتوبر 2025 تطبيق SRI غير الآمن بأنه عالي الخطورة لهذا السبب.

Trusted Types ترفع الحد الأدنى في فبراير 2026

النصف المتعلق بـ DOM-XSS من المشكلة بات له الآن حل على مستوى المتصفح. واعتبارًا من Baseline فبراير 2026، أصبحت Trusted Types متاحة عبر المتصفحات الحالية. وتُفعّل الإنفاذ عبر:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default;

بعد ذلك، ترفض مصارف DOM (DOM sinks) مثل Element.innerHTML السلاسل النصية الخام وتقبل فقط القيم الصادرة عن سياسة مسجَّلة:

const policy = trustedTypes.createPolicy("default", {
  createHTML: (input) => DOMPurify.sanitize(input),
});
el.innerHTML = policy.createHTML(userInput); // ok
el.innerHTML = userInput;                     // throws TypeError

هذا قوي حقًا. وهو أيضًا من نوع القواعد التي تنتهكها بشكل روتيني شيفرة التحليلات القديمة ومدير الوسوم، لأن تلك السكربتات تكتب إلى innerHTML وتحقن عُقد <script> كسلاسل نصية عادية. وتفعيل Trusted Types غالبًا ما يعني تعطيل مزوّد التحليلات لديك أولًا.

متتبع أول الطرف يتلاءم مع سياسة صارمة بدلًا من محاربتها

يختفي التعارض عندما يكون سكربت التحليلات صغيرًا، ومكتفيًا ذاتيًا، ومُقدَّمًا من أصلك الخاص. فالمتتبع الذي لا يحمّل أي شيفرة إضافية لا يحتاج إلى strict-dynamic ولا إلى نطاق مزوّد في قائمتك المسموح بها:

Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'; require-trusted-types-for 'script';

هذا كل شيء. لا أصل تابع لطرف ثالث، ولا تصعيد عبر nonce، ولا استثناء محفور من أجل مدير وسوم.

متتبع Monoid يبلغ حجمه حوالي 2 كيلوبايت، وليس له تبعيات، ولا يستدعي أبدًا مصرف DOM-XSS. فهو لا يكتب إلى innerHTML، ولا يحقن سكربتات، ولا يقرأ أو يضبط ملفات تعريف الارتباط أو التخزين. ويرسل طلب keepalive واحدًا لكل مشاهدة صفحة إلى /collect ويبقى خاملًا فيما عدا ذلك:

fetch('/collect', {
  method: 'POST',
  body: JSON.stringify({ site_id, path, referrer, screen_w }),
  keepalive: true,
});

ولأن الملف ثابت، يمكنك تطبيق SRI عليه دون أن يتعطل أبدًا — فالتجزئة المثبّتة تبقى صالحة حتى تختار أنت تحديث السكربت. ولأن الهوية عبارة عن تجزئة يومية أحادية الاتجاه، SHA-256(IP | UA | SALT_SECRET | YYYY-MM-DD)، فلا يوجد أي تنميط عبر المواقع يمكن لسياسة CSP متساهلة أن تسرّبه حتى.

ويتعمم هذا النمط إلى ما هو أبعد من التحليلات: فكل سكربت تزيله من قائمتك المسموح بها هو فئة من تجاوز XSS تزيلها معه. وأكثر سكربت تابع لطرف ثالث أمانًا هو السكربت الذي لا تحمّله.

المصادر

Comments

Loading comments…