內容安全政策可大幅降低跨網站指令碼攻擊的風險和影響,適用於目前的瀏覽器。
網頁的安全性模型是以同源政策為基礎。舉例來說,來自 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。如果攻擊者將程式碼插入您的網站,瀏覽器會擲回錯誤,並不會執行插入的腳本。
政策適用於多種資源
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-CSP
和 X-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 規格「Sandboxing」一節中,找到有效沙箱屬性的完整詳細資料。
中繼標記
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-ancestors
、report-uri
或 sandbox
。
避免內嵌程式碼
雖然 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 為基礎的資料竊取攻擊。
如何暫時允許內嵌指令碼和樣式
您可以將 'unsafe-inline'
新增為 script-src
或 style-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 作業。 您必須使用內嵌函式 (而非字串) 重新撰寫任何
setTimeout
或setInterval
呼叫。舉例來說,假設您的網頁含有以下內容: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'
新增為允許的來源,藉此啟用這些函式。我們強烈建議您不要這麼做,因為這會帶來程式碼注入風險。
檢舉違反政策
如要通知伺服器可能允許惡意注入的錯誤,您可以告訴瀏覽器將以 JSON 格式編寫的違規報告 POST
傳送至 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-webappsec@ 郵寄清單存檔。