XMLHttpRequest متعدد المصادر

ويمكن لصفحات الويب العادية استخدام الكائن XMLHttpRequest لإرسال البيانات واستلامها من خوادم بعيدة، ولكنها مقيّدة بسياسة المصدر نفسه. تبدأ النصوص البرمجية للمحتوى الطلبات نيابةً عن مصدر الويب الذي تم إدخال النص البرمجي للمحتوى إليه، وبالتالي تخضع النصوص البرمجية للمحتوى أيضًا لسياسة المصدر نفسها. (خضعت النصوص البرمجية للمحتوى إلى CORB منذ الإصدار 73 من Chrome وCORS بدءًا من الإصدار 83 من Chrome.) إنّ مصادر الإضافات ليست محدودة للغاية، إذ إنّ النص البرمجي الذي يتم تنفيذه في صفحة الخلفية الخاصة بإحدى الإضافات أو في علامة التبويب الأمامية يمكنه التواصل مع خوادم بعيدة خارج مصدرها الأصلي، طالما أنّ الإضافة تطلب أذونات من مصادر متعددة.

مصدر الإضافة

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

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = handleStateChange; // Implemented elsewhere.
xhr.open("GET", chrome.extension.getURL('/config_resources/config.json'), true);
xhr.send();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be evaluating an evil script!
    var resp = eval("(" + xhr.responseText + ")");
    ...
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // WARNING! Might be injecting a malicious script!
    document.getElementById("resp").innerHTML = xhr.responseText;
    ...
  }
}
xhr.send();

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

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // JSON.parse does not evaluate the attacker's scripts.
    var resp = JSON.parse(xhr.responseText);
  }
}
xhr.send();
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4) {
    // innerText does not let the attacker inject HTML elements.
    document.getElementById("resp").innerText = xhr.responseText;
  }
}
xhr.send();

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

عند تنفيذ طلبات من مصادر متعددة نيابةً عن نص برمجي للمحتوى، احرص على الحماية من صفحات الويب الضارة التي قد تحاول انتحال هوية نص برمجي للمحتوى. وعلى وجه الخصوص، ننصحك بعدم السماح للنصوص البرمجية للمحتوى بطلب عنوان 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') {
        var 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 بشكل صريح.