網頁程式開發人員適用的網站隔離功能

Mathias Bynens
Mathias Bynens

電腦版 Chrome 67 預設啟用了網站隔離這項新功能。本文將說明網站隔離的相關資訊,包括其必要性,以及為何網頁開發人員應注意這項功能。

什麼是網站隔離?

網路可用於觀看貓咪影片和管理加密貨幣錢包等,但您不會希望 fluffycats.example 存取您寶貴的加密貨幣!幸運的是,由於 Same-Origin 政策的關係,網站通常無法在瀏覽器中存取彼此的資料。不過,惡意網站可能會嘗試略過這項政策來攻擊其他網站,而且有時也會在強制執行同源政策的瀏覽器程式碼中發現安全性錯誤。Chrome 團隊的目標是盡快修正錯誤。

網站隔離是 Chrome 的安全性功能,可提供額外的安全防護措施,以降低這類攻擊的成功率。這項功能可確保不同網站的網頁一律放入不同的程序,每個程序都會在沙箱中執行,限制程序可執行的動作。此外,這項功能也會阻擋程序接收來自其他網站的特定類型機密資料。因此,有了網站隔離功能,惡意網站就更難使用 Spectre 等推測式側載攻擊,從其他網站竊取資料。當 Chrome 團隊完成其他強制執行措施後,即使攻擊者的網頁可以在其自身程序中違反部分規則,網站隔離功能也能發揮作用。

網站隔離功能可有效提高不受信任的網站在其他網站中存取或竊取您帳戶資訊的難度。這項功能可針對各種安全性錯誤 (例如近期的 Meltdown 和 Spectre 側邊管道攻擊) 提供額外的防護措施。

如要進一步瞭解網站隔離,請參閱 Google 安全性網誌上的文章

跨來源讀取封鎖

即使所有跨網站網頁都放入不同的程序,網頁仍可合法要求某些跨網站子資源,例如圖片和 JavaScript。惡意網頁可能會使用 <img> 元素,載入含有敏感資料 (例如銀行餘額) 的 JSON 檔案:

<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->

如果沒有網站隔離功能,JSON 檔案的內容會進入轉譯器程序的記憶體,此時轉譯器會發現這不是有效的圖片格式,因此不會轉譯圖片。但攻擊者接著可能會利用 Spectre 等安全漏洞,可能會讀取該記憶體區塊。

攻擊者也可以改用 <script>,將機密資料寫入記憶體:<img>

<script src="https://your-bank.example/balance.json"></script>

跨來源讀取封鎖 (CORB) 是新的安全防護功能,可防止 balance.json 的內容根據其 MIME 類型進入轉譯器處理程序記憶體。

以下詳細說明 CORB 的運作方式。網站可以向伺服器要求兩種類型的資源:

  1. 資料資源,例如 HTML、XML 或 JSON 文件
  2. 媒體資源,例如圖片、JavaScript、CSS 或字型

網站可以透過寬鬆的 CORS 標頭 (例如 Access-Control-Allow-Origin: *),從自身來源或其他來源接收資料資源。另一方面,媒體資源可從任何來源加入,即使沒有允許的 CORS 標頭也沒問題。

在下列情況下,CORB 會禁止轉譯器程序接收跨來源資料資源 (即 HTML、XML 或 JSON):

  • 資源含有 X-Content-Type-Options: nosniff 標頭
  • CORS 未明確允許存取資源

如果跨來源資料資源未設定 X-Content-Type-Options: nosniff 標頭,CORB 會嘗試嗅探回應主體,判斷該主體是 HTML、XML 還是 JSON。這是必要的做法,因為某些網路伺服器設定錯誤,會以 text/html 的形式提供圖片。

雖然 CORB 政策會封鎖資料資源,但會將該資源呈現為空白,且要求仍會在背景執行。因此,惡意網頁很難將跨網站資料拉入程序來竊取。

為了確保最佳安全性並充分運用 CORB,建議您採取下列做法:

  • 請使用正確的 Content-Type 標頭標示回應。(例如,HTML 資源應以 text/html 提供,JSON 資源應使用 JSON MIME 類型,而 XML 資源應使用 XML MIME 類型)。
  • 使用 X-Content-Type-Options: nosniff 標頭停用嗅探功能。沒有這個標頭時,Chrome 會進行快速內容分析,嘗試確認類型是否正確,但由於這會導致系統允許回應通過,以免封鎖 JavaScript 檔案等項目,因此建議您明確執行正確的操作。

詳情請參閱 針對網頁開發人員的 CORB 文章,或參閱深入的 CORB 說明

為何網頁開發人員應重視網站隔離功能?

在大部分的情況下,網站隔離是幕後的瀏覽器功能,不會直接提供給網頁程式開發人員。例如,您不需要學習新的公開網路 API。一般來說,網頁在啟用或停用網站隔離功能時,應該不會有任何差異。

不過,這項規則有例外狀況。啟用網站隔離功能可能會帶來一些微妙的副作用,進而影響您的網站。我們會維護已知的網站隔離問題清單,並在下方詳述最重要的問題。

全頁版面配置不再同步

使用網站隔離功能後,整頁版面配置就不再保證能同步,因為網頁頁框現在可以分散到多個程序。如果網頁假設版面配置變更會立即傳播至網頁上的所有框架,這可能會影響網頁。

舉例來說,假設有一個名為 fluffykittens.example 的網站,與 social-widget.example 上代管的社群媒體小工具進行通訊:

<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.width = 456;
  iframe.contentWindow.postMessage(
    // The message to send:
    'Meow!',
    // The target origin:
    'https://social-widget.example'
  );
</script>

一開始,社交小工具的 <iframe> 寬度為 123 像素。不過,FluffyKittens 頁面會將寬度變更為 456 像素 (觸發版面配置),並將訊息傳送至社交小工具,其中包含以下程式碼:

<!-- https://social-widget.example/ -->
<script>
  self.onmessage = () => {
    console.log(document.documentElement.clientWidth);
  };
</script>

每當社交小工具透過 postMessage API 收到訊息時,就會記錄其根 <html> 元素的寬度。

系統會記錄哪個寬度值?在啟用 Chrome 網站隔離功能之前,答案是 456。存取 document.documentElement.clientWidth 會強制執行版面配置,在 Chrome 啟用網站隔離功能前,這項作業原本是同步的。不過,啟用網站隔離後,跨來源社交小工具重新版面配置現在會在獨立的程序中以非同步方式進行。因此,答案現在也可以是 123,也就是舊的 width 值。

如果網頁變更跨來源 <iframe> 的大小,然後傳送 postMessage 給該網頁,在網站隔離模式下,接收框可能還不曉得在接收訊息時的新大小。更一般來說,如果網頁假設版面配置變更會立即傳播至網頁上的所有框架,這可能會導致網頁毀損。

在這個特定範例中,更穩健的解決方案會在父項影格中設定 width,並透過監聽 resize 事件來偵測 <iframe> 中的變更。

卸載處理常式可能會更頻繁地逾時

當頁框導覽或關閉時,舊的文件和內嵌的任何子頁框文件都會執行其 unload 處理常式。如果新導覽活動發生在相同的轉譯器處理程序中 (例如同源導覽),舊文件及其子畫面的 unload 處理常式可能會執行任意長的時間,才允許新導覽活動提交。

addEventListener('unload', () => {
  doSomethingThatMightTakeALongTime();
});

在這種情況下,所有影格中的 unload 處理常式都非常可靠。

不過,即使沒有網站隔離功能,部分主要框架導覽也是跨程序的,這會影響卸載處理常式的行為。舉例來說,如果您在網址列中輸入網址,從 old.example 前往 new.examplenew.example 導覽會在新程序中進行。顯示 new.example 頁面後,old.example 及其子畫面的卸載處理常式會在背景中的 old.example 處理程序中執行,且如果舊的卸載處理常式未在特定逾時時間內完成,就會終止。由於卸載處理常式可能無法在逾時前完成,因此卸載行為的穩定性較低。

啟用網站隔離功能後,所有跨網站導覽都會變成跨程序,因此不同網站的文件不會共用同一個程序。因此,上述情況適用於更多情況,而 <iframe> 中的卸載處理常常會出現上述的背景和逾時行為。

網站隔離功能帶來的另一個差異,是卸載處理常式的全新並行排序:如果沒有網站隔離功能,卸載處理常式會以嚴格的自上而下順序在各個影格中執行。但使用網站隔離時,卸載處理常式會在不同的程序中平行執行。

這些是啟用網站隔離功能的根本原因。Chrome 團隊正在努力改善常見用途的卸載處理常式可靠性。我們也發現子框架卸載處理常式無法使用特定功能的錯誤,並正在努力解決這些問題。

卸載處理常式的重要用途是傳送工作階段結束的 ping。這通常是以下方式:

addEventListener('pagehide', () => {
  const image = new Image();
  img.src = '/end-of-session';
});

考量到這項異動,更穩健的做法是改用 navigator.sendBeacon

addEventListener('pagehide', () => {
  navigator.sendBeacon('/end-of-session');
});

如要進一步控管要求,可以使用 Fetch API 的 keepalive 選項:

addEventListener('pagehide', () => {
  fetch('/end-of-session', {keepalive: true});
});

結論

「網站隔離」功能可將每個網站都隔離成獨立的程序,讓不受信任的網站更難存取或竊取您在其他網站上的帳戶資訊。在這個過程中,CORB 會嘗試將機密資料資源從轉譯器程序中移除。上述建議可確保您充分運用這些新安全性功能。

感謝 Alex Moshchuk、 Charlie Reis、 Jason Miller、 Nasko Oskov、 Philip Walton、 Shubhie Panicker 及 Thomas Steiner 提供本文的草稿版本,並提供意見回饋。