內容安全政策

內容安全政策可大幅降低跨網站指令碼攻擊的風險和影響,適用於目前的瀏覽器。

Joe Medley
Joe Medley
Mike West

瀏覽器支援

  • Chrome:25.
  • Edge:14。
  • Firefox:23.
  • Safari: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,但無法從任何其他來源下載及執行 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 級指令,可限制可做為 worker、共用 worker 或服務 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 按鈕,而其他網頁則沒有,您可以只在必要時載入按鈕程式碼。

每個指令的來源清單都是彈性的。您可以依據通訊協定 (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 標記的內容移至外部檔案,並將 javascript: 網址和 <a ... onclick="[JAVASCRIPT]"> 替換為適當的 addEventListener() 呼叫。舉例來說,您可能會從:

<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 為基礎的資料竊取攻擊

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

您可以將 'unsafe-inline' 新增為 script-srcstyle-src 指令中的允許來源,啟用內嵌指令碼和樣式。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- 關鍵字後方,將 Nonce 新增至 script-src 指令:

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

每個網頁要求都必須重新產生 Nonces,且 Nonces 不得可猜測。

雜湊的運作方式也類似。請建立指令碼的 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 以上版本時,您可以開啟開發人員工具,然後重新載入網頁。「主控台」分頁會顯示錯誤訊息,並為每個內嵌指令碼顯示正確的 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 指令就是很好的範例。不過,我們建議您改用提供預先編譯功能的模板語言,例如 Handlebars。預先編譯範本可讓使用者體驗更快,甚至比最快的執行階段實作更快,同時讓網站更安全。

如果 eval() 或其他文字轉 JavaScript 函式對應用程式至關重要,您可以在 script-src 指令中將 'unsafe-eval' 新增為允許的來源,藉此啟用這些函式。我們強烈建議您不要這麼做,因為這會帶來程式碼注入風險。

檢舉違反政策

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

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 的「推文按鈕」需要存取指令碼。將指令碼提供的指令碼移至外部 JavaScript 檔案,並使用指令 script-src https://platform.twitter.com; child-src https://platform.twitter.com
  • 其他平台也有類似的要求,可以以類似方式處理。如要測試這些資源,建議您將 default-src 設為 'none',並留意控制台,判斷需要啟用哪些資源。

如要使用多個小工具,請將指令組合如下:

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

鎖定

對於某些網站,您需要確保只能載入本機資源。以下範例會為銀行網站開發 CSP,首先從封鎖所有內容的預設政策 (default-src 'none') 開始。

網站會從 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'

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

CSP 標準開發

內容安全政策第 2 級是 W3C 建議標準。W3C 的 Web 應用程式安全性工作團隊正在開發規格的下一期:內容安全政策等級 3

如要追蹤這些即將推出的功能的相關討論,請參閱 public-websec@ 郵寄清單封存檔