介绍
实施内容安全策略 (CSP) 是减轻跨站脚本 (XSS) 风险的高效方法。然而,配置和维护策略可能会成为一个令人头疼的问题。
对于使用第三方 SDK(例如 Google 跟踪代码管理器、分析信标、支付小部件或社交共享按钮)的网站,这些脚本通常会从嵌套域动态加载(注入)其他脚本。这迫使开发人员在 script-src 指令中维护一个长而脆弱的外部域白名单。
CSP Level 3 中引入的 'strict-dynamic' 指令解决了这个问题。本文解释了它的工作原理以及如何应用它。
1. CSP中域名白名单的局限性
在旧版本的 CSP(级别 1 和级别 2)中,开发人员依赖于将允许加载和执行脚本的特定域列入白名单:
Content-Security-Policy: script-src 'self' https://www.google-analytics.com https://cdn.jsdelivr.net;
白名单的挑战
- 维护费用:
如果第三方 SDK(例如
google-analytics.com)更新其内部逻辑以从新域(例如static.cloudflareinsights.com)获取帮助程序脚本,您的网站将阻止新脚本。您必须立即更新 CSP 标头才能恢复功能。 - 白名单绕过:
如果列入白名单的 CDN(例如
cdnjs.cloudflare.com)托管具有已知漏洞的过时库或允许任意执行的脚本,攻击者可以使用它绕过您的 CSP 并执行恶意代码。
2.“严格动态”如何运作
'strict-dynamic' 指令通过建立信任链来简化脚本信任管理。
核心规则
- 信任链传播:
启用
'strict-dynamic'后,任何已受信任的脚本(通过加密的nonce或hash)都被允许动态加载新的脚本标签。浏览器自动信任受信任脚本的子代。 - 域白名单覆盖:
当浏览器在 CSP 中检测到
'strict-dynamic'时,它忽略script-src指令中的任何基于域的源表达式(如https://example.com)和'self'。信任严格由加密随机数或哈希值决定。
3. 实现示例
要使用 'strict-dynamic',您必须将其与加密 nonce (根据请求生成的随机令牌)或文件 hash 配对。
HTTP 标头配置
Content-Security-Policy: script-src 'nonce-RndOmValue123' 'strict-dynamic' 'unsafe-inline' https:;
'nonce-RndOmValue123':信任包含此匹配随机数属性的任何内联或外部脚本标记。'strict-dynamic':指示浏览器信任由随机数可信脚本动态注入的任何子脚本。'unsafe-inline' https::不支持 CSP 3 级的旧版浏览器的后备选项。当'strict-dynamic'存在时,现代浏览器会自动忽略这些选项。
HTML 标记示例
<!-- The browser executes this script because of the matching nonce attribute -->
<script nonce="RndOmValue123">
// Since this parent script is trusted, strict-dynamic allows it to inject child scripts
const script = document.createElement('script');
script.src = 'https://example.com/dynamic-library.js';
document.head.appendChild(script);
</script>
通过此设置,您无需将 https://example.com 添加到 CSP 标头。浏览器执行 dynamic-library.js 因为它是由受信任的父脚本注入的。
4. 关键考虑因素:解析器注入的脚本
请注意,某些遗留库使用 document.write('<script src="..."></script>') 注入脚本。这些解析器注入的脚本不受 'strict-dynamic' 信任,并将被浏览器阻止。
要解决此问题,您必须更新脚本加载代码以使用标准的现代 DOM API,例如 document.createElement('script')。
结论
在 CSP 中采用 'strict-dynamic' 指令可提高安全性,同时简化维护。
- 无需维护较长的第三方域白名单。
- 允许受信任的父脚本动态注入依赖项。
- 包含后备表达式 (
'unsafe-inline' https:) 以支持旧版浏览器。
考虑在项目的 CSP 配置中使用 'strict-dynamic' 和加密随机数来保护您的应用程序。

