طلبات الشبكة المتعددة المصادر

يمكن لصفحات الويب العادية استخدام واجهات برمجة التطبيقات fetch() أو XMLHttpRequest لإرسال البيانات واستلامها من خوادم بعيدة، ولكنها تقتصر على سياسة المصدر نفسه. تبدأ النصوص البرمجية للمحتوى الطلبات نيابةً عن مصدر الويب الذي تم إدخال النص البرمجي للمحتوى إليه، وبالتالي تخضع النصوص البرمجية للمحتوى أيضًا لسياسة المصدر نفسها. مصادر الإضافات غير محدودة للغاية. والنص البرمجي الذي يتم تنفيذه في مشغّل خدمات الإضافات أو علامة التبويب التي تعمل في المقدّمة يمكنه التواصل مع الخوادم البعيدة خارج مصدره، طالما أنّ الإضافة تطلب أذونات من مصادر متعددة.

مصدر الإضافة

تتوفّر كل إضافة قيد التشغيل ضمن مصدر أمان منفصل خاص بها. وبدون طلب امتيازات إضافية، يمكن أن تطلب الإضافة fetch() للحصول على الموارد ضمن عملية تثبيتها. على سبيل المثال، إذا كانت إحدى الإضافات تحتوي على ملف إعداد JSON يُسمى config.json، في مجلد config_resources/، يمكن للإضافة استرداد محتوى الملف على النحو التالي:

const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();

إذا حاولت الإضافة استخدام أصل أمان غير نفسه، على سبيل المثال https://www.google.com، لن يسمح المتصفّح بذلك، ما لم تطلب الإضافة الأذونات المناسبة من مصادر متعددة.

طلب أذونات من مصادر متعددة

لطلب الوصول إلى الخوادم البعيدة خارج مصدر الإضافة، أضِف مضيفين أو مطابقة الأنماط أو كليهما إلى القسم host_permissions في ملف manifest.

{
  "name": "My extension",
  ...
  "host_permissions": [
    "https://www.google.com/"
  ],
  ...
}

ويمكن أن تكون قيم الأذونات المشتركة المصدر أسماء مضيفين مؤهّلة بالكامل، مثل ما يلي:

  • "https://www.google.com/"
  • "https://www.gmail.com/"

أو يمكن أن تكون أنماط مطابقة، مثل هذه:

  • "https://*.google.com/"
  • "https://*/"

يسمح نمط المطابقة "https://*/" بالوصول عبر HTTPS إلى جميع النطاقات التي يمكن الوصول إليها. لاحظ أنه هنا، تشبه أنماط المطابقة أنماط مطابقة النص البرمجي للمحتوى، ولكن يتم تجاهل أي معلومات مسار تتبع المضيف.

لاحظ أيضًا أن حق الوصول يتم منحه عن طريق المضيف والمخطط. إذا كانت الإضافة تريد الوصول الآمن وغير الآمن إلى كل من بروتوكول HTTP لمضيف معيَّن أو مجموعة مضيفات معيَّنة، يجب تعريف الأذونات بشكل منفصل:

"host_permissions": [
  "http://www.google.com/",
  "https://www.google.com/"
]

Fetch() مقابل XMLHttpRequest()

تم إنشاء fetch() خصيصًا للعاملين في مجال الخدمات ويتبع اتجاهًا أوسع نطاقًا على الويب بعيدًا عن العمليات المتزامنة. تتوافق واجهة برمجة التطبيقات XMLHttpRequest() مع الإضافات خارج مشغّل الخدمات، ويؤدي استدعائها إلى تشغيل معالج الجلب لدى مشغّل خدمات الإضافة. يجب أن يفضّل العمل الجديد fetch() كلما أمكن ذلك.

الاعتبارات الأمنية

تجنُّب ثغرات النصوص البرمجية على المواقع الإلكترونية

عند استخدام الموارد التي تم استردادها عبر fetch()، يجب الحرص على عدم الوقوع ضحية البرمجة النصية على مستوى المواقع الإلكترونية في المستند أو اللوحة الجانبية أو النافذة المنبثقة التي تظهر خارج الشاشة. وعلى وجه التحديد، تجنَّب استخدام واجهات برمجة التطبيقات الخطيرة، مثل innerHTML. مثال:

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
    ...

بدلاً من ذلك، من الأفضل استخدام واجهات برمجة تطبيقات أكثر أمانًا ولا تعمل على تفعيل النصوص البرمجية:

const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);

const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;

حصر إمكانية وصول النص البرمجي للمحتوى على الطلبات من مصادر متعددة

عند تنفيذ طلبات من مصادر متعددة نيابةً عن نص برمجي للمحتوى، احرص على الحماية من صفحات الويب الضارة التي قد تحاول انتحال هوية نص برمجي للمحتوى. وعلى وجه الخصوص، ننصحك بعدم السماح للنصوص البرمجية للمحتوى بطلب عنوان URL عشوائي.

فكِّر في مثال تنفذ فيه الإضافة طلبًا من مصادر متعددة للسماح لنص برمجي للمحتوى باكتشاف سعر السلعة. قد يكون أحد الأساليب غير الآمنة جعل النص البرمجي للمحتوى يحدد المورد الدقيق الذي سيتم جلبه من خلال صفحة الخلفية.

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.contentScriptQuery == 'fetchUrl') {
      // WARNING: SECURITY PROBLEM - a malicious web page may abuse
      // the message handler to get access to arbitrary cross-origin
      // resources.
      fetch(request.url)
        .then(response => response.text())
        .then(text => sendResponse(text))
        .catch(error => ...)
      return true;  // Will respond asynchronously.
    }
  }
);
chrome.runtime.sendMessage(
  {
    contentScriptQuery: 'fetchUrl',
    url: `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
  },
  response => parsePrice(response.text())
);

في النهج المذكور أعلاه، يمكن أن يطلب النص البرمجي للمحتوى من الإضافة جلب أي عنوان URL يمكن للإضافة الوصول إليه. فقد تتمكن صفحة ويب ضارة من تزييف مثل هذه الرسائل وخداع الإضافة لمنح الإضافة إمكانية الوصول إلى موارد متعددة المصادر.

بدلاً من ذلك، صمِّم معالِجات الرسائل التي تحدّ من الموارد التي يمكن جلبها. أدناه، يتم توفير itemId فقط من خلال النص البرمجي للمحتوى، وليس عنوان URL الكامل.

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.contentScriptQuery == 'queryPrice') {
      const url = `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
      fetch(url)
        .then(response => response.text())
        .then(text => parsePrice(text))
        .then(price => sendResponse(price))
        .catch(error => ...)
      return true;  // Will respond asynchronously.
    }
  }
);
chrome.runtime.sendMessage(
  {contentScriptQuery: 'queryPrice', itemId: 12345},
  price => ...
);

تفضيل HTTPS على HTTP

بالإضافة إلى ذلك، انتبه بشكل خاص للموارد التي يتم استردادها عبر HTTP. إذا تم استخدام الإضافة على شبكة معادية، قد يعدّل أحد مهاجمي الشبكة (المعروف أيضًا باسم "man-in-the-middle") الاستجابة ويُحتمل مهاجمة الإضافة. بدلاً من ذلك، من الأفضل استخدام بروتوكول HTTPS متى أمكن ذلك.

ضبط سياسة أمان المحتوى

إذا عدَّلت سياسة أمان المحتوى التلقائية لإضافتك عن طريق إضافة السمة content_security_policy إلى ملف البيان، عليك التأكّد من السماح لأي مضيف تريد الاتصال به. علمًا أنّ السياسة التلقائية لا تفرض قيودًا على الاتصالات بالمضيفين، يجب توخي الحذر عند إضافة إما التوجيه connect-src أو default-src بشكل صريح.