保障帳戶安全

擴充功能可以存取瀏覽器中的特殊權限,因此是吸引攻擊者的目標。如果擴充功能遭駭,該擴充功能的「所有」使用者都會容易遭受惡意且不必要的侵擾。運用這些做法保護擴充功能及其使用者。

保護開發人員帳戶

擴充功能程式碼是透過 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 互動,內容指令碼必須在與網頁相同的轉譯器程序中執行。這會讓內容指令碼容易透過側邊管道攻擊 (例如),並由攻擊者入侵,前提是惡意網頁 入侵轉譯器程序。

敏感工作應透過專屬程序執行,例如擴充功能的背景指令碼。避免不小心向內容指令碼公開擴充功能權限:

註冊並清理輸入內容

限制監聽器只能掃描擴充功能預期的內容、驗證傳入資料的傳送者,並清理所有輸入內容,藉此保護擴充功能不受惡意指令碼侵擾。

擴充功能應僅為 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, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
}