擴充功能可以存取瀏覽器中的特殊權限,因此是吸引攻擊者的目標。如果擴充功能遭駭,該擴充功能的「所有」使用者都會容易遭受惡意且不必要的侵擾。運用這些做法保護擴充功能及其使用者。
保護開發人員帳戶
擴充功能程式碼是透過 Google 帳戶上傳及更新。如果開發人員的帳戶遭到入侵,攻擊者可能會將惡意程式碼直接推送到所有使用者。建議您特別建立開發人員帳戶,並啟用雙重驗證 (最好使用安全金鑰),保護這些帳戶。
保持選取群組
如果使用群組發布功能,請將群組設為只對信任的開發人員開放。請勿接受不明人士的成員資格要求。
永不使用 HTTP
要求或傳送資料時,請避免使用 HTTP 連線。假設所有 HTTP 連線都會有竊聽或包含修改內容。您應一律優先使用 HTTPS,因為其內建安全防護機制可規避大多數的中間人攻擊。
要求最低權限
Chrome 瀏覽器會限制擴充功能存取資訊清單中明確要求的權限。擴充功能應僅註冊自己仰賴的 API 和網站,藉此盡可能降低權限。任意程式碼應至少保持不變。
限制擴充功能權限可以限制潛在攻擊者能利用的內容。
跨來源 XMLHttpRequest
擴充功能只能使用 XMLHttpRequest 從權限本身或權限中指定的網域取得資源。
{
"name": "Very Secure Extension",
"version": "1.0",
"description": "Example of a Secure Extension",
"permissions": [
"/*",
"https://*.google.com/"
],
"manifest_version": 2
}
這個擴充功能會在權限中列出 "/*"
和 "https://*google.com/"
,藉此要求存取 developer.chrome.com 和 Google 子網域中的任何內容。若是擴充功能遭駭,則可能仍然只有與符合比對模式的網站互動。攻擊者將無法存取 "https://user_bank_info.com"
或與 "https://malicious_website.com"
互動。
限制資訊清單欄位
在資訊清單中加入不必要的註冊,將建立安全漏洞並讓使用者更容易看見擴充功能。將資訊清單欄位限制為擴充功能採用的欄位,並提供特定的欄位登錄。
可外部連結
使用 externally_connectable
欄位宣告擴充功能將交換資訊時使用的外部擴充功能和網頁。限制擴充功能可從外部連線至信任來源的對象。
{
"name": "Super Safe Extension",
"externally_connectable": {
"ids": [
"iamafriendlyextensionhereisdatas"
],
"matches": [
"/*",
"https://*google.com/"
],
"accepts_tls_channel_id": false
},
...
}
可透過網路存取的資源
在 web_accessible_resources
下透過網路提供資源,讓網站和攻擊者可以偵測擴充功能。
{
...
"web_accessible_resources": [
"images/*.png",
"style/secure_extension.css",
"script/secure_extension.js"
],
...
}
可用網路越容易取得,潛在攻擊者就越容易發動攻擊。請盡量減少這些檔案。
加入煽情露骨內容安全政策
在資訊清單中為擴充功能加入內容安全政策,避免跨網站指令碼攻擊。如果擴充功能只載入自身的資源,就會註冊以下項目:
{
"name": "Very Secure Extension",
"version": "1.0",
"description": "Example of a Secure Extension",
"content_security_policy": "default-src 'self'"
"manifest_version": 2
}
如果擴充功能需要納入特定主機的指令碼,可以納入:
{
"name": "Very Secure Extension",
"version": "1.0",
"description": "Example of a Secure Extension",
"content_security_policy": "default-src 'self' https://extension.resource.com"
"manifest_version": 2
}
避免使用可執行 API
應將執行程式碼的 API 替換為較安全的替代方案。
document.write() 和 innerHTML
雖然使用 document.write()
和 innerHTML
動態建立 HTML 元素會比較簡單,但會留下擴充功能以及擴充功能所依附的網頁,但有心人士也可能插入惡意指令碼。請改為手動建立 DOM 節點,並使用 innerText
插入動態內容。
function constructDOM() {
let newTitle = document.createElement('h1');
newTitle.innerText = host;
document.appendChild(newTitle);
}
eval()
請盡可能避免使用 eval()
來防範攻擊,因為 eval()
會執行傳入的所有程式碼,這可能含有惡意內容。
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();
改用安全快速的方法,例如 JSON.parse()
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();
謹慎使用內容指令碼
- 內容指令碼是擴充功能中直接與網頁互動的一部分。因此,惡意網頁可能會操控內容指令碼所依附的 DOM 部分,或惡意運用意外的網路標準行為,例如「具名項目」。
- 如要與網頁的 DOM 互動,內容指令碼必須在與網頁相同的轉譯器程序中執行。這會讓內容指令碼容易透過側邊管道攻擊 (例如),並由攻擊者入侵,前提是惡意網頁 入侵轉譯器程序。
敏感工作應透過專屬程序執行,例如擴充功能的背景指令碼。避免不小心向內容指令碼公開擴充功能權限:
- 假設內容指令碼所傳送的訊息可能是由攻擊者編寫 (例如驗證和清理所有輸入內容,並防止指令碼受到跨網站指令碼攻擊)。
- 假設傳送至內容指令碼的任何資料可能會外洩到網頁。請勿將機密資料 (例如擴充功能的密鑰、其他網路來源的資料、瀏覽記錄) 傳送至內容指令碼。
- 限制內容指令碼可觸發的權限動作範圍。請勿讓內容指令碼觸發對任意網址的要求,或是將任意引數傳遞至擴充功能 API (例如,不允許將任意網址傳遞至
fetch
或chrome.tabs.create
API)。
註冊並清理輸入內容
限制監聽器只能掃描擴充功能預期的內容、驗證傳入資料的傳送者,並清理所有輸入內容,藉此保護擴充功能不受惡意指令碼侵擾。
擴充功能應僅為 runtime.onRequestExternal
註冊,如果其預期會接收外部網站或擴充功能的通訊。請一律驗證傳送者是否與信任的來源相符。
// The ID of an external extension
const kFriendlyExtensionId = "iamafriendlyextensionhereisdatas";
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id === kFriendlyExtensionId)
doSomething();
});
即使是透過擴充功能本身透過 runtime.onMessage 事件傳送的訊息,還是應謹慎檢查,確保 MessageSender 並非來自遭盜用的內容指令碼。
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.allowedAction)
console.log("This is an allowed action.");
});
清理使用者輸入內容和傳入資料 (即使是來自擴充功能本身和已核准的來源),防止擴充功能執行攻擊者的指令碼。避免使用可執行 API。
function sanitizeInput(input) {
return input.replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
}