內容安全政策

內容安全政策可大幅降低新式瀏覽器中跨網站指令碼攻擊的風險和影響。

Joe Medley
Joe Medley
Mike West

瀏覽器支援

  • 25
  • 14
  • 23
  • 7

資料來源

網路的安全性模型是以同源政策為基礎。例如,來自 https://mybank.com 的程式碼只能存取 https://mybank.com 的資料,https://evil.example.com 則一律不允許存取。理論上,每個來源都與網頁的其他部分隔離,讓開發人員在安全沙箱中建構自己的應用程式。但實際上,攻擊者已找到幾種破壞系統的方法。

跨網站指令碼攻擊 (XSS) 攻擊,例如藉由誘騙網站傳送惡意程式碼與預期內容,略過相同來源的政策。這個問題的影響很大,因為瀏覽器信任網頁顯示的所有程式碼,都是該頁面的安全性來源的一部分。XSS 一覽表是攻擊者可能利用惡意程式碼插入惡意程式碼,用以違反這項信任方法的交叉章節。如果攻擊者成功插入「任何」程式碼,就會破壞使用者工作階段,並取得私人資訊的存取權。

本頁面將概述內容安全政策 (CSP) 做為降低新型瀏覽器中 XSS 攻擊風險和影響的策略。

CSP 的元件

如要導入有效的 CSP,請按照下列步驟操作:

  • 請使用許可清單,向客戶說明我們允許及禁止的項目。
  • 瞭解可用的指令。
  • 瞭解這些使用者會採用哪些關鍵字。
  • 限制使用內嵌程式碼和 eval()
  • 先向伺服器回報政策違規問題,再強制執行。

來源許可清單

XSS 攻擊會利用瀏覽器無法辨別出應用程式一部分的指令碼,以及第三方惡意植入的指令碼。舉例來說,本頁面底部的 Google +1 按鈕會在這個頁面的來源結構定義中載入並執行 https://apis.google.com/js/plusone.js 的程式碼。我們信任這些程式碼,但我們無法預期瀏覽器自行判斷 apis.google.com 提供的程式碼可以安全執行,但 apis.evil.example.com 的程式碼可能不是。無論來源為何,瀏覽器都會下載並執行網頁要求的任何程式碼。

CSP 的 Content-Security-Policy HTTP 標頭可讓您建立可信任內容來源的許可清單,並指示瀏覽器只執行或算繪這些來源的資源。即便攻擊者找到了用來插入指令碼的漏洞,指令碼也不會與許可清單相符,因此無法執行。

我們信任 apis.google.com 可提供有效的程式碼,而且我們相信自己也會這麼做。以下政策範例允許只有這兩個來源之一的指令碼能夠執行:

Content-Security-Policy: script-src 'self' https://apis.google.com

script-src 是一種指令,用於控管網頁的一組指令碼相關權限。這個標頭 'self' 是有效的指令碼來源,另一個是 https://apis.google.com 做為另一個指令碼來源。瀏覽器現在可以透過 HTTPS 和目前網頁的來源從 apis.google.com 下載及執行 JavaScript,但無法從任何其他來源下載及執行 JavaScript。如果攻擊者將程式碼插入網站,瀏覽器會擲回錯誤,而且不會執行插入的指令碼。

控制台錯誤:拒絕載入「http://evil.example.com/evil.js」指令碼,因為指令碼違反下列內容安全政策指令:script-src 'self' https://apis.google.com
當指令碼嘗試執行不在許可清單中的來源執行時,控制台會顯示錯誤。

政策適用於各種資源

CSP 提供一組政策指令,可讓您精細控管允許載入網頁的資源,包括上一個範例中的 script-src

下列清單概略說明第 2 級的其餘資源指令。第 3 級規格已草擬,但在主要瀏覽器中大多未實作

base-uri
限制網頁 <base> 元素中可顯示的網址。
child-src
列出 worker 和內嵌頁框內容的網址。舉例來說,child-src https://youtube.com 會啟用 YouTube 影片的嵌入,但不支援其他來源的影片。
connect-src
限制您可以使用 XHR、WebSocket 和 EventSource 連接的來源。
font-src
指定可以提供網路字型的來源。舉例來說,您可以使用 font-src https://themes.googleusercontent.com 允許 Google 的網路字型。
form-action
列出可從 <form> 標記提交的有效端點。
frame-ancestors
指定可嵌入目前網頁的來源。這個指令適用於 <frame><iframe><embed><applet> 標記。無法用於 <meta> 標記或 HTML 資源。
frame-src
這個指令已在第 2 級中淘汰,但在第 3 級中已還原。如果沒有,瀏覽器就會改回使用 child-src
img-src
定義可從來源載入來源圖片。
media-src
限制可提供影片和音訊的來源。
object-src
可控制 Flash 和其他外掛程式。
plugin-types
限制網頁可叫用的外掛程式類型。
report-uri
指定違反內容安全性政策時,瀏覽器會傳送報告的網址。這個指令無法用於 <meta> 標記。
style-src
限制網頁可以使用樣式表的來源。
upgrade-insecure-requests
指示使用者代理程式將 HTTP 變更為 HTTPS 重新編寫網址配置。此指令適用於含有大量舊網址的網站,且需要重新撰寫。
worker-src
CSP 層級 3 指令,限制能夠以工作站、共用工作站或 Service Worker 的形式載入的網址。截至 2017 年 7 月,這項指令的實作方式有限

根據預設,除非您為政策設定特定指令,否則瀏覽器會從任何來源載入相關資源,不受任何限制。如要覆寫預設值,請指定 default-src 指令。這個指令會針對任何以 -src 結尾的未指定指令定義預設值。舉例來說,如果您將 default-src 設為 https://example.com,且未指定 font-src 指令,則只能從 https://example.com 載入字型。

下列指令不會使用 default-src 做為備用方案。請注意,無法設定這些錯誤等同於允許任何項目:

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

基本 CSP 語法

如要使用 CSP 指令,請在 HTTP 標頭中列出以半形冒號分隔的指令。請務必在單一指令中列出特定類型的所有必要資源,如下所示:

script-src https://host1.com https://host2.com

以下是多個指令的範例。在本例中,應用程式會從 https://cdn.example.net 的內容傳遞網路載入所有資源,且未使用頁框內容或外掛程式:

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

實作詳情

新式瀏覽器支援無前置字串的 Content-Security-Policy 標頭。 這是建議的標頭。您在線上教學課程中看到的 X-WebKit-CSPX-Content-Security-Policy 標頭已淘汰,

CSP 是逐頁定義。您需要將要保護的每個回應一起傳送 HTTP 標頭。如此可讓您根據特定需求微調特定網頁的政策。舉例來說,如果網站上的一組網頁有 +1 按鈕,其他網頁沒有 +1 按鈕,您可以設定只在必要時載入按鈕程式碼。

各個指令的來源清單具有彈性。您可以透過配置 (data:https:) 指定來源,也可以指定僅限主機名稱的特定範圍 (example.com,這會比對該主機上的任何來源:任何配置、任何通訊埠) 到完整 URI (https://example.com:443,僅符合 HTTPS,僅 example.com 和僅通訊埠 443)。接受萬用字元,但只有配置方式、通訊埠或主機名稱最左邊的位置:*://*.example.com:* 會比對 example.com 的所有子網域 (但「不是」example.com 本身),且任何通訊埠都適用任何通訊協定。

來源清單也接受以下四個關鍵字:

  • 'none' 沒有相符的結果。
  • 'self' 會與目前的來源相符,但與子網域不相符。
  • 'unsafe-inline' 允許內嵌 JavaScript 和 CSS。詳情請參閱「避免內嵌程式碼」一文。
  • 'unsafe-eval' 允許 eval 等文字轉 JavaScript 機制。詳情請參閱「避免使用 eval()」。

這些關鍵字需使用單引號。例如,script-src 'self' (含引號) 會授權從目前主機執行 JavaScript;script-src self (無引號) 允許來自名為「self」(且「不是」目前主機) 的伺服器提供的 JavaScript,但這可能與您的預期不符。

設定網頁沙箱

還有一個值得討論的指令:sandbox。這和我們採用的其他方法有些不同,因為這限制了頁面可執行的動作,而非頁面可載入的資源。如果有 sandbox 指令,系統會將該網頁視為在含有 sandbox 屬性的 <iframe> 內部載入。這可能會對網頁造成多種效果:強制將頁面設為不重複的來源,以及防止表單提交等等。本頁有點超出本頁範圍,但您可以參閱 HTML5 規格的「沙箱」一節,瞭解有效沙箱屬性的完整詳情。

中繼標記

CSP 的偏好傳遞機制是 HTTP 標頭。不過,直接在標記中為頁面設定政策會很有幫助。請使用含有 http-equiv 屬性的 <meta> 標記:

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">

此屬性不適用於 frame-ancestorsreport-urisandbox

避免內嵌程式碼

正如 CSP 指令中使用的來源式許可清單強大,無法解決 XSS 攻擊造成的最大威脅:內嵌指令碼插入。如果攻擊者能插入的指令碼標記直接包含一些惡意酬載 (例如 <script>sendMyDataToEvilDotCom()</script>),瀏覽器就無法區分它和合法內嵌指令碼標記。為解決這個問題,CSP 會完全禁止內嵌指令碼,

此停權範圍不僅包含直接嵌入 script 標記的指令碼,還有內嵌事件處理常式和 javascript: 網址,您必須將 script 標記的內容移至外部檔案,並以適當的 addEventListener() 呼叫取代 javascript: 網址和 <a ... onclick="[JAVASCRIPT]">。例如,您可能會從:

<script>
    function doAmazingThings() {
    alert('YOU ARE AMAZING!');
    }
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>

如下所示:

<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
    alert('YOU ARE AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('amazing')
    .addEventListener('click', doAmazingThings);
});

重新編寫的程式碼不僅與 CSP 相容,還符合網頁設計最佳做法。內嵌 JavaScript 會混用結構和行為,讓程式碼感到困惑。快取及編譯方式也變得更加複雜。將程式碼移入外部資源可提升網頁效能。

此外,我們也強烈建議將內嵌 style 標記和屬性移至外部樣式表,藉此防止網站受到 CSS 資料竊取攻擊的侵擾。

如何暫時允許內嵌指令碼和樣式

如要啟用內嵌指令碼和樣式,請在 script-srcstyle-src 指令中將 'unsafe-inline' 新增為允許的來源。CSP 級別 2 也可讓您使用密碼編譯 Nonce (一次使用一次) 或雜湊,將特定內嵌指令碼新增至許可清單。如下所示。

如要使用 Nonce,請為指令碼標記提供 Nonce 屬性。其值必須與信任來源清單中的一個相符。例如:

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
    // Some inline code I can't remove yet, but need to as soon as possible.
</script>

將 Nonce 新增至 script-src 指令的 nonce- 關鍵字後方:

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

每次網頁要求都必須重新產生 Nonce,而且這些 Nonce 必須可以合理。

雜湊的運作方式類似。請改為建立指令碼本身的 SHA 雜湊,並將其新增至 script-src 指令,而不要將程式碼加入指令碼標記。舉例來說,如果您的網頁包含以下內容:

<script>alert('Hello, world.');</script>

政策必須包含:

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

sha*- 前置字串會指定產生雜湊的演算法。上述範例使用的是 sha256-,但 CSP 也支援 sha384-sha512-。產生雜湊時,請省略 <script> 標記。大寫和空白字元,包括開頭和結尾的空白字元。

產生 SHA 雜湊的解決方案支援任何語言。使用 Chrome 40 以上版本時,請開啟開發人員工具,然後重新載入頁面。「Console」(控制台) 分頁會顯示錯誤訊息,且每個內嵌指令碼都含有正確 SHA-256 雜湊。

建議不要使用 eval()

即便攻擊者無法直接插入指令碼,他們也許能誘騙應用程式將輸入文字轉換成可執行的 JavaScript,並代為執行。eval()new Function()setTimeout([string], …)setInterval([string], ...) 都是攻擊者可以用來透過插入文字執行惡意程式碼的向量。CSP 對這項風險的預設回應是完全封鎖所有這些向量。

這會對建構應用程式的方式產生以下影響:

  • 您必須使用內建的 JSON.parse 剖析 JSON,而非依賴 eval從 IE8 開始,所有瀏覽器都能使用安全 JSON 作業。
  • 您必須重新編寫使用內嵌函式 (而非字串) 發出的任何 setTimeoutsetInterval 呼叫。舉例來說,如果您的頁麵包含以下內容:

    setTimeout("document.querySelector('a').style.display = 'none';", 10);
    

    將程式碼改寫為:

    setTimeout(function () {
        document.querySelector('a').style.display = 'none';
    }, 10);
      ```
    
  • 避免在執行階段使用內嵌範本。許多範本程式庫經常會使用 new Function() 以在執行階段加快範本產生速度,進而評估惡意文字。部分架構可立即支援 CSP,如果沒有 eval,就會改回使用強大的剖析器。AngularJS 的 ng-csp 指令是很好的範例。不過,建議您改用提供預先編譯的範本語言,例如 Handlebar。比起最快的執行階段實作方式,預先編譯範本不僅能加快使用者體驗,還能提高網站的安全性。

如果應用程式需要 eval() 或其他文字對 JavaScript 函式,您可以在 script-src 指令中將 'unsafe-eval' 新增為允許來源,以啟用這些功能。強烈建議不要這麼做,因為程式碼插入的風險可能會受到影響。

檢舉政策違規行為

如要通知伺服器可能允許惡意植入的錯誤,您可以指示瀏覽器對 report-uri 指令中指定的位置,執行 POST JSON 格式的違規報告:

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

這類報表會如下所示:

{
    "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
    }
}

這份報表的實用資訊,可協助您找出違反政策的原因,包括發生在 document-uri 的網頁、該網頁的 referrer、違反頁面政策的資源 (blocked-uri)、違規的特定指令 (violated-directive),以及網頁的完整政策 (original-policy)。

僅報表

如果您才剛開始使用 CSP,建議先使用「僅限報表」模式評估應用程式狀態,再變更政策。方法是傳送 Content-Security-Policy-Report-Only 標頭,而非傳送 Content-Security-Policy 標頭:

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

在「僅報表」模式中指定的政策不會封鎖受限制的資源,但會傳送違規報告到您指定的位置。您甚至可以傳送「兩個」標頭,以便在監控另一項政策時強制執行。強制執行現行政策的同時,測試 CSP 變更是不錯的方法,例如為新政策啟用報告功能、監控違規報告並修正所有錯誤;如果對新政策感到滿意,就可以開始強制執行政策。

現實世界的使用率

為應用程式製定政策的第一步,是評估應用程式載入的資源。瞭解應用程式的結構後,請根據自身需求建立政策。以下各節將介紹一些常見用途,以及根據 CSP 規範支援的決策程序。

社群媒體小工具

  • Facebook 的「喜歡」按鈕提供多種實作選項。建議您使用 <iframe> 版本,避免網站上的其餘部分與沙箱隔離。且需要 child-src https://facebook.com 指令才能正常運作。
  • X 的 Tweet 按鈕需要存取指令碼。將提供的指令碼移到外部 JavaScript 檔案中,然後使用指令 script-src https://platform.twitter.com; child-src https://platform.twitter.com
  • 其他平台有類似的要求,可以透過類似的方式處理。如要測試這些資源,建議您設定 'none'default-src,並觀察主控台,判斷需要啟用哪些資源。

如要使用多個小工具,請按照下列方式合併指令:

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

鎖定

針對部分網站,建議您確認只能載入本機資源。以下範例會從封鎖所有內容 (default-src 'none') 的預設政策著手,為銀行網站開發 CSP。

網站會在 https://cdn.mybank.net 從 CDN 載入所有圖片、樣式和指令碼,並使用 XHR 連線至 https://api.mybank.com/ 以擷取資料。它使用頁框,但僅適用於網站本機上的網頁 (無第三方來源)。網站沒有 Flash,也沒有字型和額外項目。可傳送的最嚴格 CSP 標頭如下:

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

僅限 SSL

以下是論壇管理員的 CSP 範例,他們想確保論壇中的所有資源都只使用安全連線載入,但卻沒有程式碼的使用經驗,而且沒有重新編寫使用內嵌指令碼和樣式的第三方論壇軟體:

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

雖然在 default-src 中指定 https:,但指令碼和樣式指令不會自動繼承該來源,每個指令都會覆寫該資源類型的預設值。

CSP 標準開發

內容安全政策等級 2 是 W3C 建議標準。W3C 的 Web 應用程式安全性工作小組正在開發規格的下一篇疊代「內容安全政策等級 3」。

如要參與這些即將推出功能的討論,請參閱 public-applicationsec@ 郵寄清單封存