使用嚴格的內容安全政策 (CSP) 減少跨網站指令碼攻擊 (XSS)

Lukas Weichselbaum
Lukas Weichselbaum

瀏覽器支援

  • Chrome:52。
  • Edge:79,
  • Firefox:52。
  • Safari:15.4。

資料來源

跨網站指令碼攻擊 (XSS), 有機會將惡意指令碼插入網頁應用程式。 過去十年來規模最大的網路安全性漏洞

內容安全政策 (CSP) 是一種額外的安全措施,有助於緩解 XSS。如要設定 CSP, 將 Content-Security-Policy HTTP 標頭加到網頁,並設定 控制使用者代理程式為該網頁載入哪些資源。

本頁說明如何根據 Nonce 或雜湊來使用 CSP 來緩解 XSS。 而不是經常使用的主機許可清單型 CSP,這些通訊內容經常會離開 暴露在 XSS 中,因為它們可以在大部分設定中略過

重要字詞:nonce 是隨機數字,只能用來標記 <script> 標記為可信任。

重要字詞:雜湊函式是用於轉換輸入的數學函式 值壓縮為雜湊的壓縮數值。您可以使用雜湊值 (例如 SHA-256) 標記內嵌項目 <script> 標記為可信任。

以 Nonce 或雜湊為基礎的內容安全政策通常稱為 嚴格 CSP。應用程式使用嚴格 CSP 時,攻擊者只要找到 HTML 插入安全漏洞通常無法用來強制瀏覽器執行 是否含有惡意指令碼這是因為只有嚴格的 CSP 經過雜湊處理的指令碼或指令碼,若產生正確的 Nonce 值, 伺服器,因此攻擊者如果不知道正確的 Nonce,就無法執行指令碼 特定回應。

為何應使用嚴格 CSP?

如果您的網站已有類似 script-src www.googleapis.com 的 CSP, 但跨網站的成效可能不如預期這類 CSP 稱為 allowlist CSP。這類廣告需要大量自訂,且能 略過

以加密 Nonce 或雜湊為基礎的嚴格 CSP 可避免這些錯誤。

嚴格的 CSP 結構

基本的嚴格內容安全政策會使用下列其中一種 HTTP 回應 標題:

以 Nonce 為基礎的嚴格 CSP

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
以 Nonce 為基礎的嚴格 CSP 運作方式。

以雜湊為基礎的嚴格 CSP

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

下列屬性會使這類 CSP 稱為「嚴格」因此可確保安全性:

  • 使用 Nonce 'nonce-{RANDOM}' 或雜湊 'sha256-{HASHED_INLINE_SCRIPT}' 指出網站的開發人員信任要在哪些 <script> 代碼中執行 使用者的瀏覽器設定。
  • 設定 'strict-dynamic' ,減少自動部署 Nonce 或雜湊型 CSP 的負擔 允許執行受信任的指令碼建立的指令碼。這也 解除封鎖多數第三方 JavaScript 程式庫和小工具。
  • 這項設定不是根據網址許可清單,所以不受 常見的 CSP 略過
  • 封鎖不受信任的內嵌指令碼,例如內嵌事件處理常式或 javascript: URI。
  • 這項政策會限制 object-src 停用危險的外掛程式,例如 Flash。
  • 這會限制 base-uri 禁止插入 <base> 標記。這麼做可以避免 不讓攻擊者變更從相對 URL 載入的指令碼位置。
,瞭解如何調查及移除這項存取權。

採用嚴格的 CSP

如要採用嚴格 CSP,您需要:

  1. 決定應用程式是否應設定 Nonce 或雜湊型 CSP。
  2. 複製「Strict CSP 結構」部分中的 CSP 並進行設定 做為回應標頭
  3. 重構 HTML 範本和用戶端程式碼,以移除 。
  4. 部署 CSP。

您可以使用 Lighthouse (7.3.0 以上版本,加上 --preset=experimental 標記) 最佳做法稽核 請檢查你的網站是否採用 CSP, 對 XSS 而言具有極大效果

燈塔
  回報在強制執行模式下找不到 CSP 的警告。
如果你的網站沒有 CSP,Lighthouse 會顯示這項警告。

步驟 1:決定需要 Nonce 或雜湊型 CSP

以下是兩種嚴格 CSP 類型的運作方式:

Nonce 型 CSP

使用 Nonce 的 CSP 時,您在「執行階段」產生隨機數字,並將該數字納入 並將該 CSP 與網頁中的每個指令碼標記建立關聯。攻擊者 不能在網頁中加入或執行惡意指令碼,因為 猜猜該指令碼的正確隨機數字只有在數字類型為 100 是無法猜測的情況,而且會針對每次回應在執行階段中重新產生。

針對伺服器上轉譯的 HTML 網頁,使用 Nonce 為基礎的 CSP。針對這些網頁 您可以為各則回應建立新的隨機號碼

雜湊型 CSP

如果是雜湊式 CSP,每個內嵌指令碼標記的雜湊都會新增至 CSP。 每個指令碼都有不同的雜湊。攻擊者無法加入或執行惡意程式碼 指令碼,因為該指令碼的雜湊必須放在您的 CSP,以便其執行。

針對靜態提供的 HTML 網頁或需要 快取。舉例來說,您可以針對單頁網頁使用雜湊式 CSP 以及建構應用程式 靜態提供,而不在伺服器端轉譯。

步驟 2:設定嚴格 CSP,並準備指令碼

設定 CSP 時,您可以選擇下列幾種選項:

  • 報表專用模式 (Content-Security-Policy-Report-Only) 或強制執行模式 (Content-Security-Policy).在「報表專用」模式下,CSP 不會封鎖 導致網站沒有任何問題,但您可以看到錯誤 回報任何已遭封鎖的項目在本機上操作 設定 CSP 就沒有影響,因為這兩種模式都會顯示 錯誤。強制執行模式可協助您 封鎖 CSP 區塊草稿,因為封鎖資源 網頁似乎損毀。報表專用模式的後續程序最為實用 (請參閱步驟 5)。
  • 標頭或 HTML <meta> 標記。針對本機開發,<meta> 標記可提供更多 ,方便您微調 CSP,並快速查看其對網站的影響。 不過,請注意以下幾點:
    • 稍後在實際工作環境中部署 CSP 時,建議您將 CSP 設為 HTTP 標頭
    • 如要在「僅報表」模式下設定 CSP,您必須將 CSP 設為 標頭,因為 CSP 中繼標記不支援報表專用模式。

選項 A:以 Nonce 為基礎的 CSP

設定下列 Content-Security-Policy HTTP 回應 輸入 Deployment 的標頭:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

為 CSP 產生 Nonce

Nonce 是每次載入網頁時只會使用一次的隨機數字。Nonce 只有在攻擊者無法猜測 Nonce 值時,CSP 才能降低 XSS。A 罩杯 CSP Nonce 必須符合下列條件:

  • 經過加密的高強度隨機值 (最好長度為 128 位元以上)
  • 為每次回覆產生新的
  • Base64 編碼

以下範例說明如何在伺服器端架構中新增 CSP Nonce:

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

<script> 元素中加入 nonce 屬性

如果使用以 Nonce 為基礎的 CSP,則每個 <script> 元素都必須 具有與隨機 Nonce 相符的 nonce 屬性 透過 CSP 標頭指定的值。所有指令碼都可以相同 Nonce。第一步是將這些屬性加進所有指令碼 CSP 可以允許這類物件。

選項 B:雜湊型 CSP 回應標頭

設定下列 Content-Security-Policy HTTP 回應 輸入 Deployment 的標頭:

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

多個內嵌指令碼的語法如下: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'

動態載入來源指令碼

由於只有內嵌指令碼支援不同瀏覽器上的 CSP 雜湊, 您必須使用內嵌指令碼,以動態方式載入所有第三方指令碼。 各種瀏覽器都沒有完整支援來源指令碼雜湊。

內嵌指令碼的範例。
CSP 允許存取
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
為了讓這個指令碼能夠執行,您必須計算內嵌指令碼的雜湊值 並新增至 CSP 回應標頭,取代 {HASHED_INLINE_SCRIPT} 預留位置。如要減少雜湊數量,您可以合併所有內嵌項目 把指令碼轉換為單一指令碼如要瞭解實際運作方式,請參閱此 範例 與其程式碼
遭到 CSP 封鎖
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP 會封鎖這些指令碼,因為只有內嵌指令碼才能進行雜湊處理。

指令碼載入註意事項

內嵌指令碼範例會新增 s.async = false,確保 foo 會在 bar 之前執行 bar 會先載入。在這個程式碼片段中:s.async = false 系統在載入指令碼時不會封鎖剖析器,因為指令碼 以動態方式新增;剖析器只會在指令碼執行期間停止,例如 將 async 個指令碼用於。不過使用這個程式碼片段 注意事項:

  • 文件完成前,可能執行一或兩個指令碼執行 下載。如果您希望文件在時限內準備就緒 指令碼執行後,請等待 DOMContentLoaded 事件 請附加指令碼如果這會造成成效問題 由於指令碼未能盡早開始下載,請提早在網頁上使用預先載入代碼
  • defer = true 不會執行任何動作。如果需要 行為,視需要手動執行指令碼。

步驟 3:重構 HTML 範本和用戶端程式碼

內嵌事件處理常式 (例如 onclick="…"onerror="…") 和 JavaScript URI (<a href="javascript:…">) 可用來執行指令碼。也就是說, 攻擊者一旦發現 XSS 錯誤,便可植入這類 HTML 並執行惡意程式碼 JavaScript。以 Nonce 或雜湊為基礎的 CSP 禁止使用這類標記。 如果您的網站使用上述任一模式,請將其重構為安全網頁 替代解決方案

如果您在上一個步驟中啟用 CSP,就會在下列位置看到 CSP 違規事項: 則每次 CSP 封鎖不相容的模式時,才會更新控制台。

Chrome 開發人員控制台中的 CSP 違規報告。
控制台發生程式碼遭封鎖的錯誤。

在大多數情況下,解決方法非常簡單:

重構內嵌事件處理常式

CSP 允許存取
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP 允許使用 JavaScript 註冊的事件處理常式。
遭到 CSP 封鎖
<span onclick="doThings();">A thing.</span>
CSP 會封鎖內嵌事件處理常式。

重構 javascript: URI

CSP 允許存取
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP 允許使用 JavaScript 註冊的事件處理常式。
遭到 CSP 封鎖
<a href="javascript:linkClicked()">foo</a>
CSP 會封鎖 JavaScript:URI。

從 JavaScript 移除 eval()

如果您的應用程式使用 eval(),將 JSON 字串序列化轉換為 JS 物件,您應該將這類例項重構為 JSON.parse()更快

如果無法移除所有 eval() 使用情形,還是可以設定嚴格以 Nonce 為基礎 CSP,但必須使用 'unsafe-eval' CSP 關鍵字,讓 政策安全性略低

在這個嚴格 CSP 中,您可以找到這些重構的例子及更多範例 codelab:

步驟 4 (選用):新增支援舊版瀏覽器的備用方案

瀏覽器支援

  • Chrome:52。
  • Edge:79,
  • Firefox:52。
  • Safari:15.4。

資料來源

如需支援舊版瀏覽器,請按照下列步驟操作:

  • 使用 strict-dynamic 時,必須新增 https: 做為先前的備用方案 Safari 版本。步驟如下:
    • 所有支援 strict-dynamic 的瀏覽器都會忽略 https:, 因此,政策效力不會降低
    • 在舊版瀏覽器中,只有來自外部來源的指令碼才能載入 HTTPS 來源與嚴格的 CSP 相比,這個安全性降低了,但仍 可避免一些常見的 XSS 原因,例如插入 javascript: URI。
  • 為確保與舊版瀏覽器 (4 年以上) 相容,您可以新增 unsafe-inline 可做為備用方案。所有近期使用的瀏覽器都會忽略 unsafe-inline 表示 CSP Nonce 或 Hash。
,瞭解如何調查及移除這項存取權。
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

步驟 5:部署 CSP

確認 CSP 並未封鎖 在本機開發環境中,您可以將 CSP 部署至測試環境,然後再部署到 正式環境:

  1. (選用) 使用 Content-Security-Policy-Report-Only 標頭。僅限報表模式非常實用 預先測試可能的破壞性變更,例如正式環境中新的 CSP 開始強制執行 CSP 限制在「僅限報表」模式下,CSP 不會 影響應用程式的行為,但瀏覽器仍會產生控制台錯誤 如果發現任何模式與 CSP 不相容,就會通報違規。 方便您瞭解使用者可能發生的情況如要 請參閱 Reporting API
  2. 確定 CSP 不會對使用者造成網站中斷後, 使用 Content-Security-Policy 回應標頭部署 CSP。三 建議使用 HTTP 標頭伺服器端設定 CSP,因為這 而非 <meta> 標記。完成這個步驟後,CSP 就會啟動 保護應用程式不受 XSS 保護
,瞭解如何調查及移除這項存取權。

限制

嚴格 CSP 通常會提供強大的額外安全防護,協助 減少 XSS。在大多數情況下,CSP 大幅減少了受攻擊面, 拒絕 javascript: URI 等危險模式。不過,視類型而定 的 CSP (nonce、雜湊,包含或不含 'strict-dynamic') CSP 也無法為您的應用程式提供安全防護:

  • 如果您未取得指令碼,但系統已直接插入內文或 該 <script> 元素的 src 參數。
  • 如果動態建立指令碼的位置插入了 (document.createElement('script')),包括任何程式庫函式 ,可根據其引數值建立 script DOM 節點。這個 包含一些常見的 API,例如 jQuery 的 .html(),以及 .get() 和 JPEG 中的 .post() <3.0.
  • 如果舊版 AngularJS 應用程式中有插入範本。攻擊者 插入 AngularJS 範本時 執行任意 JavaScript
  • 如果政策包含 'unsafe-eval',系統會將插入至 eval()setTimeout(),以及一些其他不常使用的 API。

開發人員和安全性工程師應特別留意這類問題 找出這些模式。詳情請參閱

延伸閱讀