跨來源 XMLHttpRequest

一般網頁可以使用 XMLHttpRequest 物件,從遠端伺服器傳送及接收資料,但會受到同源政策限制。內容指令碼會代表內容指令碼插入的網路來源發出要求,因此內容指令碼也須遵守同源政策。(自 Chrome 73 起,內容指令碼便已套用 CORB ,自 Chrome 83 起則套用 CORS)。擴充功能的來源則沒有這麼多限制,只要擴充功能要求跨來源權限,在擴充功能背景網頁或前景分頁中執行的指令碼,就能與來源以外的遠端伺服器通訊。

擴充功能來源

每個執行中的擴充功能都有自己的獨立安全來源。擴充功能不需要求額外權限,即可使用 XMLHttpRequest 取得安裝目錄中的資源。舉例來說,如果擴充功能包含名為 config.json 的 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),除非擴充功能已要求適當的跨來源權限,否則瀏覽器會禁止這項操作。

要求跨來源權限

只要在資訊清單檔案的「permissions」部分中加入主機或主機比對模式 (或兩者),擴充功能就能要求存取來源以外的遠端伺服器。

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

跨來源權限值可以是完整的主機名稱,例如:

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

也可以是比對模式,例如:

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

「https://*/」的相符模式允許透過 HTTPS 存取所有可連線的網域。請注意,這裡的比對模式與內容指令碼比對模式類似,但主機後方的任何路徑資訊都會遭到忽略。

另請注意,存取權是依主機和通訊協定授予。如果擴充功能想同時安全和不安全地存取特定主機或一組主機,就必須分別聲明權限:

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

限制內容指令碼對跨來源要求的存取權

代表內容指令碼執行跨來源要求時,請務必防範惡意網頁嘗試模擬內容指令碼。特別是,請勿允許內容指令碼要求任意網址。

舉例來說,假設擴充功能執行跨來源要求,讓內容指令碼找出商品的價格。其中一種 (不安全) 方法是讓內容指令碼指定背景網頁要擷取的確切資源。

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

在上述方法中,內容指令碼可以要求擴充功能擷取擴充功能有權存取的任何網址。惡意網頁可能會偽造這類訊息,誘騙擴充功能授予跨來源資源的存取權。

請改為設計訊息處理常式,限制可擷取的資源。如下所示,只有 itemId 是由內容指令碼提供,而非完整網址。

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-srcdefault-src 指令時,請務必謹慎。