一般網頁可以使用 fetch() 或 XMLHttpRequest API,從遠端伺服器傳送及接收資料,但會受到同源政策限制。內容指令碼會代表內容指令碼插入的網路來源發出要求,因此內容指令碼也須遵守同源政策。擴充功能來源則沒有這麼多限制。只要擴充功能要求主機權限,在擴充功能 Service Worker 或前景分頁中執行的指令碼,就能與來源以外的遠端伺服器通訊。
擴充功能來源
每個執行中的擴充功能都有自己的獨立安全來源。擴充功能不必要求額外權限,就能呼叫 fetch() 取得安裝目錄中的資源。舉例來說,如果擴充功能包含名為 config.json 的 JSON 設定檔,位於 config_resources/ 資料夾中,擴充功能可以透過下列方式擷取檔案內容:
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 存取所有可連線的網域。請注意,這裡的比對模式與內容指令碼比對模式類似,但主機後方的任何路徑資訊都會遭到忽略。
另請注意,存取權是依主機和通訊協定授予。如果擴充功能想同時安全和不安全地存取特定主機或一組主機,就必須分別聲明權限:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() 與 XMLHttpRequest()
fetch() 專為服務工作人員建立,並遵循更廣泛的網路趨勢,不再使用同步作業。擴充功能 (不含 Service Worker) 支援 XMLHttpRequest() API,呼叫該 API 會觸發擴充功能 Service Worker 的擷取處理常式。新作品應盡可能採用 fetch()。
安全性考量
避免跨網站指令碼攻擊
使用透過 fetch() 擷取的資源時,螢幕外文件、側邊面板或彈出式視窗應小心,以免成為跨網站指令碼攻擊的受害者。具體來說,請避免使用危險的 API,例如 innerHTML。例如:
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-src 或 default-src 指令時,請務必謹慎。