内容安全策略与你的CMP:让CSP与同意协同工作
必须保持一致的两层安全机制
内容安全策略(CSP)告诉浏览器,页面可以从哪些来源加载脚本、样式、图像、框架和网络请求。同意管理平台则做相反的工作:根据用户的选择,决定这些脚本中哪些可以运行。当二者被分别配置时,就会发生冲突。要么CMP因被CSP阻止而无法加载,要么为了迁就CMP和广告标签而将CSP放得太宽,以至于它什么也保护不了。
本指南说明CMP通常需要哪些CSP指令、nonce如何与脚本注入相互作用,以及如何在不破坏横幅的前提下推出变更。
CMP通常需要CSP提供什么
具体要求因供应商而异,因此请始终从你的CMP自己的文档入手。一般来说,请检查这些指令:
script-src— CMP脚本加载自的域名,以及它加载的任何其他脚本文件。connect-src— CMP通过fetch或XHR调用的端点,例如配置、供应商列表、地理位置和同意日志端点。style-src— 样式表所在域名;如果CMP注入<style>元素或设置style属性,还可能需要内联样式。img-src— CMP使用的徽标、图标以及任何不含追踪的像素。font-src— 如果横幅加载自己的字体。frame-src— 如果CMP使用iframe,例如用于跨域同意共享。
在TCF网站上,请记住CMP还会在页面上创建一个名为__tcfapiLocator的定位器框架。该框架创建时没有远程来源,因此通常不需要frame-src条目,但值得在测试中确认。
内联存根问题
大多数CMP会要求你在页面头部放置一小段内联脚本——即存根——以便其他脚本在主CMP加载之前将调用排入队列。严格的CSP会阻止内联脚本,除非你明确允许。你有三种选择:
- nonce。为每个响应生成一个随机值,作为
'nonce-...'添加到CSP头中,并添加到存根的<script>标签上。这是动态页面最灵活的选择。 - 哈希。将存根确切内容的SHA-256哈希添加到
script-src。这对静态网站很有效,但对存根的任何改动,哪怕只是空白字符,都会使其失效。 - 自托管存根,作为你自己域名上的外部文件,由
'self'覆盖。这需要注意确保它加载得足够早。
不要为图省事使用'unsafe-inline'。它会允许所有内联脚本,从而消除CSP所提供的大部分保护。
nonce与CMP注入的脚本
在获得同意之前阻止脚本的CMP,通常的做法是先扣留脚本,之后再注入。在基于nonce的CSP下,这些被注入的脚本也需要被允许。有两种常见做法:
- nonce传播。CMP从自己的脚本标签中读取nonce,并将其添加到它注入的每个脚本上。请检查你的CMP是否支持。
'strict-dynamic'。该关键字告诉支持它的浏览器信任由已受信任脚本加载的脚本。它简化了使用标签管理器和会注入脚本的CMP时的工作,但同时也意味着任何你信任的脚本都可以加载其他脚本,因此要谨慎地信任。
标签管理器也会引出同样的问题。如果你通过标签管理器加载广告和分析标签,请遵循其关于nonce的文档,并确保CMP和标签管理器采用一致的方法。
广告标签让CSP更难
程序化广告会从许多域名加载脚本和框架,而且该列表会随需求合作伙伴的变化而变化。发布商通常会做出两种选择之一:
- 在没有程序化广告的页面使用严格的CSP,在投放广告的页面使用更宽松的策略。
- 采用聚焦于仍可管理的指令的策略,例如
frame-ancestors、object-src 'none'和base-uri,同时让脚本来源保持更宽泛。
无论哪种方式,CMP都应出现在明确的允许列表中,而不是仅由通配符覆盖,这样CSP的变更就不会悄悄使横幅失效。
使用Report-Only模式推出
首先使用Content-Security-Policy-Report-Only头。浏览器会报告违规而不阻止任何内容,这样你就能看到新策略会破坏什么。
- 以report-only模式部署策略,并配置报告端点。
- 在干净的浏览器配置文件中加载页面,并走完完整的同意流程:首次访问、全部接受、全部拒绝、自定义选择、撤回。
- 查看违规报告和浏览器控制台,找出被阻止的CMP资源。
- 调整策略,然后切换到强制执行。
- 强制执行后保持报告开启,以发现回归问题。
CSP冲突的症状
- 横幅从不出现,控制台显示CMP脚本的CSP违规。
- 横幅出现但没有样式,因为
style-src阻止了其CSS。 - 选择未被保存,因为
connect-src阻止了同意日志调用。 - 同意后广告或分析从不加载,因为被注入的脚本缺少nonce。
__tcfapi未定义,因为内联存根被阻止了。
核心要点
- 为你的CMP检查
script-src、connect-src、style-src、img-src和frame-src。 - 用nonce或哈希而不是
'unsafe-inline'来允许内联存根。 - 确保在获得同意后注入的脚本获得nonce,或被
'strict-dynamic'覆盖。 - 在强制执行之前,于report-only模式下测试每一条同意路径。
- 将CMP明确列入允许列表,使CSP变更无法使其失效。