Chrome 的擴充功能系統強制執行相當嚴格的預設內容安全政策 (CSP)。
政策限制很簡單:指令碼必須以內嵌方式移至獨立的 JavaScript 檔案、內嵌事件處理常式需轉換為使用 addEventListener
,且 eval()
已停用。Chrome 應用程式有更嚴格的政策,我們非常滿意這些政策提供的安全性屬性。
但我們也知道,許多程式庫都會使用 eval()
和 eval
類似的結構 (例如 new Function()
) 來最佳化效能並簡化表達效果。範本程式庫特別容易採用這種實作方式。雖然有些 (例如 Angular.js) 本身支援 CSP,但許多熱門架構尚未更新至與擴充功能「無 eval
」世界相容的機制。因此,移除對該功能的支援功能已證實開發人員問題超出預期。
本文件介紹沙箱機制,做為在專案中納入這些程式庫的安全機制,同時兼顧安全性。為求簡潔,我們會在整個使用「擴充」一詞,但這個概念同樣適用於應用程式。
為什麼要使用沙箱?
eval
在擴充功能中執行並不安全,因為執行的程式碼可以存取擴充功能高權限環境中的所有內容。大量功能強大的 chrome.*
API 可供使用,可能會對使用者的安全性和隱私權造成重大影響。簡單來說,竊取資料是我們最擔心的。所提供的解決方案是沙箱,eval
可在無法存取擴充功能資料或擴充功能的高價值 API 的情況下執行程式碼。沒有資料,不必使用 API 也不成問題。
具體做法是在擴充功能套件中列出採用沙箱機制的特定 HTML 檔案。每當系統載入沙箱頁面時,都會將網頁移至專屬來源,並拒絕存取 chrome.*
API。如果我們透過 iframe
將此沙箱頁面載入擴充功能,我們可以傳遞訊息,並以某種方式處理這些訊息,並等待系統傳回結果。這個簡單的訊息傳遞機制讓我們只需要在擴充功能的工作流程中,安全地加入 eval
驅動的程式碼。
建立及使用沙箱。
如要直接深入瞭解程式碼,請擷取沙箱範例擴充功能並加以停用。這是一個以 Handlebars 範本建構為基礎的小型訊息 API 實例範例。它應該可提供您所需的所有資訊。如果你有需要進一步說明 可以參考下面的範例
列出資訊清單中的檔案
每個希望在沙箱中執行的檔案,都必須新增 sandbox
屬性,才能列在擴充功能資訊清單中。這是非常重要的步驟,很容易忘記,因此請仔細檢查您的沙箱檔案是否已列在資訊清單中。在這個範例中,我們為檔案「sandbox.html」採用沙箱機制。資訊清單項目如下所示:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
載入採用沙箱機制的檔案
為了在沙箱檔案中進行有趣的工作,我們必須在可以使用擴充功能程式碼處理的環境中載入該檔案。在這裡,sandbox.html 已透過 iframe
載入擴充功能的事件頁面 (eventpage.html)。eventpage.js 包含的程式碼會在每次有人按下瀏覽器動作時,將訊息傳送至沙箱,方法是找出頁面上的 iframe
並在其 contentWindow
上執行 postMessage
方法。該訊息是包含兩個屬性的物件:context
和 command
。我們稍後會深入探討這兩種方式。
chrome.browserAction.onClicked.addListener(function() {
var iframe = document.getElementById('theFrame');
var message = {
command: 'render',
context: {thing: 'world'}
};
iframe.contentWindow.postMessage(message, '*');
});
postMessage
API 的一般資訊,請參閱 MDN 的 postMessage
說明文件 。閱讀的內容相當完整,值得。請特別注意,只有在資料可序列化時,才會來回傳遞資料。例如函式則不是。從事危險行為
載入 sandbox.html
時,系統會載入 Handlebar 程式庫,並以 Handlebar 建議的方式建立及編譯內嵌範本:
<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 當做擴充功能使用者介面的一部分。透過 innerHTML
插入物件並不會帶來重大的安全性風險,因為即使透過一些巧妙的攻擊,完整盜用沙箱程式碼,也無法將危險指令碼或外掛程式內容插入高權限擴充功能內容。
這個機制讓建立範本非常簡單,而且不限於範本。凡是不符合嚴格內容安全政策原理規定的程式碼,都能採用沙箱機制;事實上,對於「可以」正確執行的擴充功能元件,沙箱通常也很有用,以便將程式的每個元件限制為正確執行所需的最低權限組合。2012 年 Google I/O 大會的「編寫安全網頁應用程式和 Chrome 擴充功能」簡報提供了這些技巧的實用範例,花 56 分鐘的時間撥出時間。