跨來源網路要求

一般網頁可以使用 fetch()XMLHttpRequest API 從遠端伺服器傳送及接收資料,但會受到相同來源政策的限制。內容指令碼會代表插入內容指令碼的網路來源啟動要求,因此內容指令碼也必須符合相同的來源政策。擴充功能來源不受限制。在擴充功能服務工作站或前景分頁中執行的指令碼,只要擴充功能要求跨來源權限,即可與來源外的遠端伺服器通訊。

擴充功能來源

每個執行中的擴充功能都位於各自的安全性來源中。而無需要求額外權限,擴充功能即可呼叫 fetch() 來取得安裝中的資源。例如,如果擴充功能在 config_resources/ 資料夾中包含名為 config.json 的 JSON 設定檔,擴充功能即可擷取檔案內容,如下所示:

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

如果擴充功能嘗試使用非本身的安全性來源 (例如 https://www.google.com),則瀏覽器會禁止該擴充功能,除非擴充功能要求適當的跨來源權限。

要求跨來源權限

如要要求存取擴充功能來源以外的遠端伺服器,請在資訊清單檔案的 host_permissions 部分新增主機和/或比對模式

{
  "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() API 支援服務工作站以外的擴充功能,呼叫此 API 會觸發擴充功能服務工作站的擷取處理常式。新工作應盡可能優先使用 fetch()

安全性考量

避免跨網站指令碼攻擊

使用透過 fetch() 擷取的資源時,螢幕外文件、側邊面板或彈出式視窗應謹慎避免受害者跨網站指令碼攻擊。具體來說,請避免使用 innerHTML 等危險 API。例如:

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;
    ...

建議您改用較安全的 API,不要執行指令碼:

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;

限制只能存取跨來源要求的內容指令碼

代表內容指令碼執行跨來源要求時,請務必防範企圖冒用內容指令碼的惡意網頁。請特別注意,不允許內容指令碼要求任意網址。

我們舉個例子說明:擴充功能執行跨來源要求,讓內容指令碼探索商品價格。其中一種不安全的做法是讓內容指令碼指定背景頁面擷取的確切資源。

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