XMLHttpRequest متقاطع

صفحات وب معمولی می‌توانند از شیء XMLHttpRequest برای ارسال و دریافت داده‌ها از سرورهای راه دور استفاده کنند، اما آنها توسط همان سیاست مبدا محدود شده‌اند. اسکریپت‌های محتوا درخواست‌هایی را از طرف مبدا وب که اسکریپت محتوا به آن تزریق شده است، آغاز می‌کنند و بنابراین اسکریپت‌های محتوا نیز تابع همان سیاست مبدا هستند. (اسکریپت‌های محتوا از کروم ۷۳ مشمول CORB و از کروم ۸۳ مشمول CORS شده‌اند.) مبداهای افزونه‌ها چندان محدود نیستند - یک اسکریپت که در صفحه پس‌زمینه یا تب پیش‌زمینه یک افزونه اجرا می‌شود، می‌تواند با سرورهای راه دور خارج از مبدا خود ارتباط برقرار کند، تا زمانی که افزونه مجوزهای مبدا-متقابل را درخواست کند.

منشأ پسوند

هر افزونه‌ی در حال اجرا، در مبدأ امنیتی جداگانه‌ی خود قرار دارد. بدون درخواست امتیازات اضافی، افزونه می‌تواند از 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، صفحه پس‌زمینه شما باید مراقب باشد که قربانی اسکریپت‌نویسی بین‌سایتی نشود. به طور خاص، از استفاده از APIهای خطرناک مانند موارد زیر خودداری کنید:

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();

در عوض، API های امن‌تری را ترجیح دهید که اسکریپت اجرا نمی‌کنند:

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 بازیابی می‌شوند. اگر افزونه شما در یک شبکه‌ی متخاصم استفاده می‌شود، یک مهاجم شبکه (معروف به "مرد میانی" ) می‌تواند پاسخ را تغییر دهد و به طور بالقوه به افزونه‌ی شما حمله کند. در عوض، تا حد امکان HTTPS را ترجیح دهید.

تنظیم سیاست امنیتی محتوا

اگر سیاست امنیتی محتوای پیش‌فرض برای برنامه‌ها یا افزونه‌ها را با اضافه کردن ویژگی content_security_policy به مانیفست خود تغییر دهید، باید مطمئن شوید که هر میزبان که می‌خواهید به آن متصل شوید، مجاز است. اگرچه سیاست پیش‌فرض، اتصال به میزبان‌ها را محدود نمی‌کند، اما هنگام اضافه کردن صریح دستورالعمل‌های connect-src یا default-src مراقب باشید.