在 Chrome 擴充功能中使用 eval

Chrome 擴充功能系統會強制執行相當嚴格的預設內容安全政策 (CSP)。 政策限制非常簡單明瞭:指令碼必須從線上移至獨立項目 JavaScript 檔案、內嵌事件處理常式必須轉換為 addEventListener,而 eval() 為 已停用。Chrome 應用程式有更嚴格的政策,我們很滿意這些政策提供的安全性屬性。

不過,我們也瞭解,許多程式庫都會使用 eval()eval 類似的結構 (例如 new Function()),以便提升效能並簡化表達方式。模板化程式庫特別容易採用這種實作方式。雖然部分 (例如 Angular.js) 支援 CSP 目前許多熱門架構尚未更新為相容的機制 擴充功能勇闖 eval 的世界。因此,對於開發人員而言,移除這項功能的支援比預期更麻煩

本文件介紹採用沙箱機制,方便你在專案中加入這些程式庫 而不會犧牲安全性為了簡化說明,我們會在整篇文章中使用「擴充功能」一詞,但這個概念同樣適用於應用程式。

為什麼要採用沙箱機制?

eval 對擴充功能內不安全,因為執行的程式碼能夠存取 排除擴充功能的高權限環境我們提供多種功能強大的 chrome.* API,這些 API 可能會嚴重影響使用者的安全性和隱私權;簡單的資料外洩只是其中最輕微的問題。優惠解決方案是沙箱,可讓 eval 在不存取 擴充功能的資料或擴充功能的高價值 API。沒有資料、沒有 API,也沒問題。

我們會將擴充功能套件中的特定 HTML 檔案列為沙箱檔案,藉此達成這項目標。每當載入沙箱網頁時,系統都會將該網頁移至獨特來源,並拒絕存取 chrome.* API。如果我們透過 iframe 將這個沙箱頁面載入擴充功能,就可以傳遞訊息,讓擴充功能以某種方式對這些訊息採取行動,然後等待它傳回結果。這個簡單的訊息機制讓我們能安全納入以 eval 為主的所有功能 將程式碼寫入擴充功能工作流程中

建立及使用沙箱。

若要直接參閱程式碼,請取得沙箱擴充功能範例, 關閉。這是建構在控制列之上的小型訊息 API 實際範例 現在應該能取得所有範本資料,方便您踏出第一步。如想進一步瞭解,我們可以一起查看這個範例。

列出資訊清單中的檔案

凡是應該在沙箱中執行的檔案,都必須在擴充功能資訊清單中列出 sandbox 屬性。這是一個很容易遺漏的重要步驟,因此請仔細檢查沙箱檔案是否列在資訊清單中。在本範例中,我們要巧妙地為檔案採用沙箱機制 「sandbox.html」資訊清單項目如下所示:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

載入沙箱檔案

為了對沙箱檔案進行有趣的操作,我們需要在 都能透過擴充功能的程式碼進行處理這個例子中的 sandbox.html 已載入 透過 iframe 的擴充功能事件頁面 (eventpage.html)。eventpage.js 包含程式碼,可在點選瀏覽器動作時,透過尋找網頁上的 iframe,並在其 contentWindow 上執行 postMessage 方法,將訊息傳送至沙箱。訊息是包含兩個屬性的物件:contextcommand。我們稍後會深入探討。

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
如需 postMessage API 的一般資訊,請參閱 postMessage 說明文件。這份文件相當完整,值得一讀。特別注意,只有可序列化的資料才能來回傳遞。例如函式就不是。

從事危險活動

載入 sandbox.html 時,它會載入 Handlebars 程式庫,並按照 Handlebars 建議的方式建立及編譯內嵌範本:

<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>Hello, !</h1>
  </div>
</script>
<script>
  var templates = [];
  var source = document.getElementById('hello-world-template').innerHTML;
  templates['hello'] = Handlebars.compile(source);
</script>

這不會失敗!雖然 Handlebars.compile 最終使用 new Function,但一切正常 與預期完全相同,最後在 templates['hello'] 中取得編譯的範本。

傳回結果

我們會設定訊息監聽器,讓這個範本可接收來自事件頁面的指令,以便使用這個範本。我們會使用傳入的 command 來判斷應執行的動作 (您可以想像不只是轉譯,也許還要建立範本?或許可以透過某種方式管理?),而 context 會直接傳入範本進行算繪。經過算繪的 HTML 會傳回至活動頁面,以便擴充功能日後執行實用操作:

<script>
  window.addEventListener('message', function(event) {
    var command = event.data.command;
    var name = event.data.name || 'hello';
    switch(command) {
      case 'render':
        event.source.postMessage({
          name: name,
          html: templates[name](event.data.context)
        }, event.origin);
        break;

      // case 'somethingElse':
      //   ...
    }
  });
</script>

回到事件頁面,我們會收到這則訊息,並利用傳遞的 html 資料執行有趣的操作。在這種情況下,我們會透過桌面通知回應,但完全可以安全地將這段 HTML 用於擴充功能的 UI。插入方式: innerHTML 不會帶來重大安全性風險,即使是沙箱模式完全破壞也一樣 一旦受到一些巧妙的攻擊,就無法將危險的指令碼或外掛程式內容 進階權限的擴充功能背景資訊

這個機制可讓您輕鬆建立範本,但當然不限於範本。任何在嚴格的內容安全政策下無法立即運作的程式碼,都可以進行沙箱處理;事實上,將擴充功能的元件沙箱處理,通常會正確執行,以便將程式的每個部分限制為執行所需的最小權限集。2012 年 Google I/O 大會的「Writing Secure Web Apps and Chrome Extensions」簡報提供了一些實用的例子,值得花 56 分鐘的時間觀看。