⌘K 切换工具
CSP

Content Security Policy生成器

生成Content-Security-Policy头部。

local
csp-generator

Build a Content-Security-Policy. Common keywords: 'self' 'none' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' data: https: blob:

§01 关于此工具

概述

Content-Security-Policy 告诉浏览器它可以从哪些来源加载资源。它的实际价值比名字听上去要窄一些:CSP 并不修复跨站脚本,它围堵跨站脚本。如果一次注入穿过了你的输出编码,那么一份严格的策略就是阻止被注入的脚本执行、或者阻止它把偷到的东西发往攻击者服务器的那道防线。

这个头部本身是一串用分号分隔的指令,每条指令指明一种资源类型,以及允许这种资源使用的那些来源。这个工具为每条指令提供一个输入框,预先填好一份合理的起始策略,并在你输入的同时同步显示拼装出来的头部。

使用方法

  1. 从预填的策略开始——default-src 'self'object-src 'none'base-uri 'self'frame-ancestors 'none'。对于自己托管全部资源的站点来说,这是一个合理的下限。
  2. 一条指令一条指令地加上你真正需要的那些源。多个来源之间用空格分隔。
  3. 把某条指令留空就等于省略它;被省略的指令会回退到 default-src
  4. 复制头部,先以 Content-Security-Policy-Report-Only 部署。读违规报告、修策略,然后再换成强制生效的那个头名。

来源关键字

关键字含义
'self'文档自身的源——同协议、同主机、同端口
'none'什么都不允许。只有作为唯一来源时才有意义
'unsafe-inline'允许内联的 <script> / <style> 以及内联事件处理器
'unsafe-eval'允许 evalnew Function,以及给 setTimeout 传字符串参数
'strict-dynamic'信任由已受信脚本创建出来的脚本。会让该指令里的主机白名单被忽略
data:允许 data: URI。用在 img-src 上合理,用在 script-src 上危险
https:任何走 HTTPS 的源。范围太宽——通常说明这份策略需要收紧
blob:允许 blob: URL,从生成的代码创建 Web Worker 时需要它

哈希('sha256-…')和 nonce('nonce-…')是 'unsafe-inline' 的两种严格替代品。一个哈希覆盖的是某一段内容永远不会变化的内联脚本,这适合静态生成的站点。而 nonce 是每个响应都重新生成一次的随机值,这适合服务端渲染的页面。你不能在静态文件托管上使用 nonce,因为那里根本没有一个按请求执行的环节来生成它。

容易写错的那几条指令

base-uri 不受 default-src 兜底。缺了它,一个被注入的 <base href> 标签就能把页面上每一个相对 URL 都重定向到攻击者的主机——你的 script 标签也在其中——而它完全不需要注入任何脚本本身。把它设成 'self''none'

form-action 同样不受 default-src 兜底。缺了它,一个被注入的表单可以把用户的输入 POST 到另一个源去。

frame-ancestors 取代 X-Frame-Options。两者说法冲突时,现代浏览器听 CSP 的,所以只设那个旧头部,会让较新的浏览器完全不受你本意想表达的任何约束。

object-src 'none' 值得显式写上。插件内容是一条遗留下来的执行路径,在一个新站点上没有任何正当用途。

connect-src 是人们最常忘掉的一条。它管辖 fetch、XHR、WebSocket 和 sendBeacon——而这恰恰就是一个被注入的脚本用来把数据外传出去的途径。一个收得很紧的 script-src 配上一个大开着的 connect-src,等于把本来要关上的那扇门原样留在那里。

示例

一个自己托管全部资源、不含任何第三方脚本的静态站点:

default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none';
form-action 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:;
font-src 'self'; connect-src 'self'; upgrade-insecure-requests

同一个站点再加上一个分析标签,只放开它确实需要的那几个源,而不是直接写 https:

script-src 'self' https://www.googletagmanager.com;
connect-src 'self' https://www.google-analytics.com

收集违规报告

Report-Only 模式只有在你真的会去读报告的时候才有用。现有两种机制,浏览器的支持情况是分裂的,所以上线时通常两个都发。

report-uri /csp-report 是较老的那条指令。它已经被废弃,但支持面仍然最广,它会 POST 一个 JSON 体,描述被拦下的资源、拦下它的那条指令,以及文档的 URL。

report-to 是它的替代品。它指向一个由单独的 Reporting-Endpoints 响应头配置的报告分组,这样一个端点就能同时承接 CSP、废弃特性和干预三类报告。

不管你用的是哪一个,都要预期会有噪音。浏览器扩展会往页面里注入脚本和样式,而它们造成的违规在报告里和你自己代码造成的违规长得一模一样。真正值得当成信号的是:在同一个文档 URL、同一条指令下,出现在许多不同用户身上的那种违规;而一条孤零零指向 chrome-extension: 协议的 blocked-uri,那不过是某个人的密码管理器。

这两条指令在 <meta> 标签里都不可用。上报必须依赖真正的 HTTP 响应头。

说明

upgrade-insecure-requests 会在子资源请求发出之前,把 http:// 改写成 https://。它是混合内容页面的一种迁移辅助手段,不能替代把 URL 本身改对,而且它不作用于跳转到其他站点的导航。

style-src 通常是最后一个才去掉 'unsafe-inline' 的地方,而且往往也是值得留着的那一个。内联 style 属性没法用哈希覆盖——你得用 'unsafe-hashes',再为每一个不同的属性值各配一个哈希——而 CSS 注入本身是一个比脚本注入窄得多的问题。先把力气花在 script-src 上,是更划算的取舍。

在切到强制生效之前,先用 Content-Security-Policy-Report-Only 部署。一份只严了一条指令的策略不会优雅地降级:它会对每一个执行它的浏览器背后的访客悄无声息地把页面弄坏,而你会从用户那里、而不是从自己的测试里得知这件事。

如果你想看某个线上站点当前实际发送了什么,HTTP 头信息检查 可以显示任意 URL 的响应头。

FAQ
这个工具会校验我的策略吗?
不会。它只是把你填进去的字段拼成头部文本;它不检查你写的来源是否可达,也不检查你的站点是不是还能正常工作。唯一可靠的校验方式是在真实站点上用 Report-Only 模式,下面有说明。
为什么我同时加了哈希之后,'unsafe-inline' 就被忽略了?
那是规范规定的行为,不是缺陷。如果 script-src 里含有任何哈希或 nonce 来源,能理解它们的浏览器就会完全忽略 'unsafe-inline'。这是刻意设计的升级路径——老浏览器拿到宽松的关键字,新浏览器拿到严格的清单。
frame-src 和 frame-ancestors 有什么区别?
frame-src 控制你的页面可以嵌入什么。frame-ancestors 控制谁可以嵌入你的页面——它是 CSP 用来取代 X-Frame-Options 的东西,而且和大多数指令不同,它不受 default-src 兜底。
如果我把其他每条指令都写全了,还需要 default-src 吗?
还是值得设置。default-src 是那些你没写的 fetch 类指令的兜底,其中也包括你上线之后才被加进规范的新指令。把它设成 'self',意味着将来新增的指令会默认关闭,而不是默认放开。
做好的头部应该放在哪里?
作为 HTTP 响应头,最好放在 CDN 或 Web 服务器上,这样每个响应都会带上它。`<meta http-equiv>` 标签也能用,但它无法表达 frame-ancestors 或 report-uri,而且只有在解析器读到它那一行之后才开始生效。