XMLHttpRequest ממקורות שונים

דפי אינטרנט רגילים יכולים להשתמש באובייקט XMLHttpRequest כדי לשלוח ולקבל נתונים מרחוק. שרתים, אבל הם מוגבלים על ידי אותה מדיניות מקור. התחלת בקשות באמצעות סקריפטים של תוכן בשם מקור האינטרנט שהוחדר אליו סקריפט התוכן, ולכן התוכן הסקריפטים כפופים גם למדיניות המקור הזהה. (סקריפטים של תוכן כפופים ל-CORB מ-Chrome 73 ומ-CORS מ-Chrome 83). מקורות התוסף לא מוגבלים כל כך – סקריפט הרצה בדף רקע או בכרטיסייה בחזית של תוסף יכולה לדבר אל שרתים מרוחקים שנמצאים מחוץ ל- המקור שלו, כל עוד התוסף מבקש הרשאות ממקורות שונים.

מקור התוסף

כל תוסף פעיל קיים במקור אבטחה נפרד משלו. בלי לשלוח בקשה נוספת ההרשאות, התוסף יכול להשתמש ב-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 שהתוסף ל- יש גישה אל. דף אינטרנט זדוני עלול להיות מסוגל לזייף הודעות כאלה ולהעביר את התוסף במרמה למתן גישה למשאבים ממקורות שונים.

במקום זאת, צריך לעצב handlers של הודעות שמגבילים את המשאבים שניתן לאחזר. בהמשך, רק סקריפט התוכן סופק על ידי 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.