授权与合规声明
本文全部操作对象均为自建隔离靶场(本机容器或隔离虚拟机),涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款,须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离,不含任何攻击步骤、利用载荷与绕过手法,请勿将文中环境指向任何非自有系统。
一、先分清 CSP 管的是「加载」还是「执行」
1.1 两行一模一样的头,两个不一样的结果
先说一个具体的场面。有人在自建靶场里给站点加 CSP,加得很朴素:nginx 里多写一行add_header。加完打开两个页面:A 页面图表区整块白了,B 页面同一个模板,图表正常。
他把两边的响应头抓下来逐字比对,那一行完全相同。头一样,结果不一样,于是他判断是浏览器实现有差异。
这个判断是错的,而错的地方不在浏览器。差别在 B 页面的模板里——那是一份旧模板,里面还留着一条script-src。因为这条script-src存在,A 页面那行default-src在 B 页面对脚本请求的判定里根本没有参与。它不是总开关,只填空缺;同一条头给出两种结果,不是浏览器不一致,是两条策略里「空缺」的位置不同。
出处先说清:本文引用 W3C《Content Security Policy Level 3》。截至 2026-10-25,它的状态是W3C Working Draft,日期 2026-09-16,地址https://www.w3.org/TR/CSP3/。它是工作草案,不是 Recommendation,条文还会改,所以本文每处原文只对 2026-09-16 这一版负责。
1.2 一条策略里混着四种东西
很多人把 CSP 当成一份资源白名单。这个印象在早期版本勉强够用,放到 Level 3 就不够了:一条策略里的指令按作用对象至少分成四类。
| 类别 | 管的问题 | 本文涉及的指令 | 有default-src兜底吗 |
|---|---|---|---|
| fetch 指令 | 某一类请求能不能发出去 | script-src-elem、script-src-attr、img-src、connect-src、font-src、object-src | 有 |
| 文档指令 | 文档自身的某些行为受不受限 | base-uri、sandbox | 没有 |
| 导航指令 | 谁能嵌我、我能往哪里提交 | frame-ancestors、form-action | 没有 |
| 上报指令 | 违规信息往哪里送 | report-uri、report-to | 没有 |
最右一列是全文的骨架:只有第一类有兜底,后三类没有。default-src的覆盖范围比多数人以为的窄得多——它只管「请求发不发得出去」,不管「这份文档能不能被嵌」。
分类不是按危险程度分的,是按判定发生在哪一刻分的:fetch 指令在请求发起前判定,手里有一个目标 URL 可比对;导航指令没有这个 URL,frame-ancestors判的是「能不能被某个祖先嵌进去」,也就无从兜底。
1.3 「我加了 CSP」这句话本身没有信息量
要判断一条 CSP 在做什么,至少问清三件事。
加在哪里。响应头还是meta元素——meta装不下某些指令(第六章)。
加了几条。一个响应可以有多条策略头,多条之间不是「后一条覆盖前一条」(第三章)。
每条里写了哪些指令。写了哪几条 fetch 指令,剩下的才轮到default-src;'none'那处的列表是否真为空;Report-Only 那条是否根本没在拦(第二、五、四章)。
少问一个,结论就可能反过来。
本章可以带走的一句:CSP 不是一份资源白名单,而是按请求类型分开判的一组规则;说「我加了 CSP」,信息量等于零。
二、误解一:把default-src当成总开关
2.1 「no inheritance」这五个字要读三遍
规范讲这件事只用了一句话,位置很不起眼,但它是否掉后面一半误读的钥匙:
There is no inheritance. If a script-src directive is explicitly specified, for example, then the value of default-src has no influence on script requests.
两个词要抓住。no inheritance:这里没有继承。has no influence:不是影响变小,不是优先级更低,是没有任何影响。
我们平时说「默认值」,模型是继承:子级没写用父级,写了用自己,父级还在那儿放着。CSP 不是这个模型,它是填表:没填的格子default-src帮你填上,填过的它连看都不看。
Content-Security-Policy: default-src 'self'; script-src https://cdn.example.com⚠️代码待验证
这条策略有两处容易读错。第一,script-src这一格被填过了,default-src从此不参与脚本判定。脚本的放行范围只有「https://cdn.example.com」,'self'不在里面——它不允许加载同源脚本。写它的人想的是「同源照旧,再加一个 CDN」,按规范语义做的是「把脚本源换成 CDN 一个域」。第二,default-src剩下的权力还在:图片、字体、样式、连接、媒体、对象没被单独填,仍由它兜着。
所以在本条策略内部,再写一条script-src是替换,不是叠加。这和「多写一条更保险」的直觉正好相反。
2.2 规范给的等价写法里,没有script-src
default-src 'self'等价于什么,规范直接给了展开式,值得原样抄一遍:
Content-Security-Policy: connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; object-src 'self'; script-src-elem 'self'; script-src-attr 'self'; style-src-elem 'self'; style-src-attr 'self'; worker-src 'self'⚠️代码待验证
一共 12 项:
| 序号 | 等价清单里的指令 | 它管的是哪一类请求 |
|---|---|---|
| 1 | connect-src | 脚本发起的连接(XHR、WebSocket 这一类) |
| 2 | font-src | 字体文件 |
| 3 | frame-src | 这一页里嵌进去的子框架 |
| 4 | img-src | 图片 |
| 5 | manifest-src | Web App Manifest |
| 6 | media-src | 音频与视频 |
| 7 | object-src | 插件类对象 |
| 8 | script-src-elem | 脚本元素 |
| 9 | script-src-attr | 元素上的内联脚本属性 |
| 10 | style-src-elem | 样式元素 |
| 11 | style-src-attr | 元素上的内联样式属性 |
| 12 | worker-src | Worker 类脚本 |
有一个必须原样保留、不能「顺手改对」的细节:清单里是script-src-elem和script-src-attr,不是script-src;是style-src-elem和style-src-attr,不是style-src。
因为在这套判据里「脚本」本来就不是一个东西,是两个:脚本元素和内联脚本属性走两条判定路径,各有各的指令。script-src不在清单里,是因为它在这套结构里统管这两者,不是被统管的一项——把它展开进清单会把两层压成一层。
实际影响有两处。想单独管住内联脚本属性,有专门的指令可用,不必动整体。排查时看报告里的effective-directive:写script-src-attr说明问题出在属性上,写script-src-elem才是脚本元素,改法完全不同。script-src与这两条如何回落需要另开一篇,本篇只把「清单是四位不是两位」钉住。
2.3 它兜不到的东西,比兜得到的更值得记
default-src只是 fetch 指令的兜底。规范的措辞很干脆:frame-ancestors、base-uri、form-action、sandbox、report-uri/report-to这些都不在 fetch 指令的兜底范围内。
这五条里最容易误解的是frame-ancestors。有人写default-src 'none',觉得「什么都禁了,肯定也没法被嵌了」。实际是外部资源全加载不了,但被别的站点用框架嵌进来这件事一点没受影响——「我能加载谁」和「谁能嵌我」是两个方向。
还有一处更细的例外:prefetch、preconnect 这类资源提示请求不归任何单一条 fetch 指令管,而是「governed by the union of servers allowed in all of a policy’s directives’ source lists」——由这条策略里所有指令源列表允许的服务器的并集来管。后面还跟了一句更要紧的:没写default-src时,这类请求「will always be allowed」。
所以只写了img-src的策略,对 prefetch 完全不管。CSP 逐条生效,没写到的地方就是没写到。
本章可以带走的一句:default-src只填空缺,不覆盖已写;它管的是 fetch 指令,管不到文档、导航和上报,也管不住 prefetch 这类资源提示。
三、误解二:多条策略取「谁更宽」
3.1 两条头不是两套方案,是两道闸
第二个高频误读出在多条策略并存时:网关加一条宽的,应用加一条严的,最后应该以应用那条为准;或者后加的覆盖先加的。
规范说得明白:
Enforcing both policies means that a potential connection would have to pass through both unscathed.
adding additional policies to the list of policies to enforce can only further restrict the capabilities of the protected resource.
第一句给判据:一个连接要「毫发无伤地穿过两条」。第二句给方向:增加策略只能进一步限制,不可能放宽。合起来就是一个词——交集。
「两个都过」听起来温和,后果经常不温和。网关写default-src 'self',应用写default-src 'self'; img-src https://img.example.com,后者看起来更宽;但交集之后,图片域只在应用那条里成立,网关仍只认同源,图片照样加载不了。要放宽只能改第一条本身,或者撤掉它。
3.2 脚本那句话最短,也最容易被跳过
交集这件事,规范在脚本这里给了一句最短的话:
Script will only load if it meets both policy’s criteria.
短,但藏了一个顺序问题,正是本章与第二章的接缝。「两条都要满足」满足的是各自展开之后的判据:每条策略先用自己的指令填空缺(第二章),形成完整判据,再取交集(本章)。
顺序决定排查的第一步动作:不能跨策略借用。A 策略写了script-src、B 策略没写但有default-src,那么 B 对脚本的判据来自它自己的default-src,不来自 A 的script-src。
报告若不指明触发的是哪条策略,就得把每条单独展开一遍。
3.3 两个后果,都不轻
白屏来得莫名其妙。两条头分开看都没问题,合起来不允许,这时单改任何一条都改不动。判断方法很简单:临时去掉其中一条,看页面是否恢复。
你看到的头,可能不是用户拿到的头。规范有一句容易被忽略:同一资源的不同表示可以带不同的策略——「A server MAY send different Content-Security-Policy header field values with different representations of the same resource」。落地后果是多机房、灰度、缓存变体都可能让同一个 URL 带不同的头。
本章可以带走的一句:多条策略是取交集,只有收紧一条路;把「再加一条宽一点的」当成补丁,方向一开始就反了。
四、误解三:把 Report-Only 当成「先开着看看会不会拦」
4.1 它在判定里是被跳过的,不是被降级的
这个误读最容易出在灰度上线:先用 Report-Only 发出去观察,没问题再切成强制。流程对,错的是理解——很多人以为它是「软性拦截」,拦还是会拦,只是不显示、只上报。
规范在判定算法里给了一句很硬的话:
If policy’s disposition is “report”, then skip to the next policy.
skip to the next policy——跳过,看下一条。这句在判定循环内部,也就是说disposition为report的策略,在「要不要拦这个请求」这个判断里根本没被问过。不是「问了但答案被忽略」,是「没问」。
由此带出一个直接连着第三章的推论:一条 Report-Only 策略不参与交集。它在取交集之前就被跳过了,既不放宽也不收紧。写一条比 enforce 更严的,页面行为不会有任何变化;写一条更宽的也一样。
拿「加了 Report-Only 页面还是白的」说明「两条策略取了交集」,是把两件事搞混:白屏一定来自 enforce 那条。
4.2 它能回答什么,不能回答什么
能回答的:这条策略的语法能不能被接受;如果切成强制,哪些请求会被挡;上报通道通不通。
不能回答的:挡下来之后业务还能不能跑。这是它最大的盲区。一个被挡的请求,在 Report-Only 阶段是「一条报告」,在 enforce 阶段是「一个功能坏了」——隔的不是程度,是性质。「报告里没有关键请求」推不出「切成强制没问题」,风险恰恰常在「被拦了但没人知道那是个关键请求」这一侧;报告数量本身也受采样与去重影响,不能当风险分数用。
4.3 从「只报」到「真拦」,四步的顺序不能乱
| 对比项 | Content-Security-Policy | Content-Security-Policy-Report-Only |
|---|---|---|
| 处置(disposition) | enforce | report |
| 是否参与拦截判定 | 参与 | 不参与,判定时被跳过 |
| 是否阻断请求 | 会阻断 | 不阻断 |
| 是否与其他策略取交集 | 会 | 不会 |
能否写在meta里 | 能 | 不能 |
| 主要用途 | 执行策略 | 观察这条策略会拦什么 |
第一步,先只报,内容与将来 enforce 的那条逐字相同。差一个字,观察结果就不作数。常见错误是「Report-Only 里先不写那么严」,观察到的就不是将来要上线的策略。
第二步,收集一段时间,把「会拦」的请求按指令归类。单位是指令不是 URL,因为改法跟着指令走:img-src改图床,connect-src改接口域名,script-src-attr得改代码。
第三步,拿归类结果去改业务,而不是回头改策略。报告一出来就想着把域名补进策略,当然能消掉报告,但抹掉的是观察结果,不是问题。
第四步,切成 enforce,上报通道保留。保留上报不是可选项:enforce 之后仍会有新页面带来新违规,没有上报只能等用户反馈。
本章可以带走的一句:Report-Only 是「不拦、只记」,它在判定里被直接跳过,连交集都不参与;把它当成「软性拦截」是最常见的一处误读。
五、误解四:'none'并列、空指令、重定向路径
5.1'none'不是加锁,是表示「列表为空」
第四个误读藏在写法层面:策略看着更严了,实际更松了。
The ‘none’ keyword has no effect when other source expressions are present. That is, the list « ‘none’ » does not match any URL. A list consisting of « ‘none’, https://example.com », on the other hand, would match https://example.com/.
拆成两句读。第一句说'none'和其他源表达式同时存在时没有效果。第二句给对照:« 'none' »匹配不了任何 URL,而« 'none', https://example.com »会匹配https://example.com/。
源列表内部是「或」的关系,任一项匹配即匹配。'none'永远匹配不上,是恒假项;恒假项和别的项做「或」,结果等于别的项——它被吸收了。
这不是「加了一道锁」,是「写了一个不生效的项」。有人为了保险把'none'和允许的域并列,期待「既允许这个域又禁止别的」,实际是「只允许这个域,'none'那句白写」。结果一样,但理解错了下一步会出问题:他可能又往里加一个域,以为'none'会拦住它。
| 写法 | 源列表里实际有什么 | 结果 |
|---|---|---|
script-src 'none' | 只有'none'一项 | 任何 URL 都不匹配,全部拦下 |
script-src 'none' https://example.com | 'none'加一个主机源 | 等价于只写了example.com |
script-src | 空 | 等价于'none',任何 URL 都不匹配 |
script-src 'self' | 一个关键字源 | 同源可加载 |
第三行是下一节的主角。
5.2 空指令是最像「没写」的「全禁」
An empty source list (that is, a directive without a value: script-src, as opposed to script-src host1) is equivalent to a source list containing ‘none’, and will not match any URL.
没有值的指令等价于源列表里只有'none'。注意方向:空不等于「没写」,空等于「全禁」。这个语义危险,是因为它在配置文件里长得太像「没配」:
Content-Security-Policy: script-src 'self' https://cdn.example.com Content-Security-Policy: script-src 'none' https://cdn.example.com Content-Security-Policy: script-src⚠️代码待验证
第一种正常。第二种里'none'白写,等价于第一种去掉'self'。第三种最要命:语法合法、语义全禁,而扫一眼都像「这一行什么都没写」。
工程上它几乎总是从模板变量来的。script-src {{ .ScriptSources }}在变量有值时正常,为空时渲染出script-src,语法合法、语义全禁、页面白屏,而模板文件里看不出异常。排查这类问题,第一件事是把渲染后的头发出来看,而不是读模板。
还有一种反向误解:以为「写了指令名就算设了一条规则」。按上面那句原文,它设的是一条全禁规则。指令名的存在只说明这一格被填了,填进去的是空。
5.3 重定向之后,路径那一段不再参与判定
the matching algorithm ignores the path component of a source expression if the resource being loaded is the result of a redirect
配套例子是img-src example.com example.org/path。读法是:源表达式可以带路径,主机部分一直有效;路径部分只在直接命中时有效,一旦这次加载是重定向的结果,路径部分就被忽略。
这是判定规则,不是技巧,本文只讲读法。实际影响很具体:写https://static.example.com/assets/想表达「只允许这个目录」,它在重定向场景下退化成「允许static.example.com整个主机」。你以为限定到了一个目录,实际是一个主机。
由此有一条纪律:不要用源表达式里的路径来表达安全边界。路径只能表达直接请求时的前缀,真要限制到目录,那是服务端的事。
完整版对照资料:这份资料把 CSP3 工作草案里 fetch 指令、文档指令、上报指令的归属逐条列成一张对照表,和本章「哪条指令归谁管、哪条有兜底」是同一套骨架,可以直接拿去核自己站点的那几条头。放在资料包里,扫码即可获取:
本章可以带走的一句:'none'只在列表为空时有意义,空列表等于'none';源表达式里的路径是最不可靠的那一段,重定向一发生它就不作数了。
六、部署位置和头之间的关系,也会改结论
6.1meta装不下三样东西,也装不下 Report-Only
前五章讲「策略里写了什么」,这一章讲「写在哪里」——位置本身带限制。
The Content-Security-Policy-Report-Only header is not supported inside a meta element. Neither are the report-uri, frame-ancestors, and sandbox directives.
一句话里四个「不行」:Report-Only 整个不支持;report-uri、frame-ancestors、sandbox三个指令不支持。共同点是meta是文档自己说的话,而有些判断必须在文档说话之前完成。
frame-ancestors最典型。它判的是「这个文档能不能被某个祖先嵌进来」,这个判断发生在解析之前——浏览器得先决定要不要把它放进那个框。而meta是解析到那一行才被读到的,那时框已经建好了。所以它写在meta里不是「不推荐」,是「读不到」。
| 能力 | 响应头 | meta元素 |
|---|---|---|
| Report-Only 策略 | 支持 | 不支持 |
report-uri | 支持 | 不支持 |
frame-ancestors | 支持 | 不支持 |
sandbox | 支持 | 不支持 |
report-to | 支持 | 不在上述限制的列举范围内,本文不进一步推断 |
| 解析后修改是否生效 | 不适用(响应到达时已固定) | 不生效 |
| 生效时机 | 响应到达即生效 | 元素被解析时生效,越靠前越好 |
倒数第三行是有意留白:那句限制只点了三个指令加一个头类型,没把report-to列进去;但「没列进禁止名单」和「确定可用」是两件事。
6.2meta的时机、重复指令、大小写
Modifications to the content attribute of a meta element after the element has been parsed will be ignored.
元素解析完成后再改content属性,改动被忽略。规范顺带建议meta尽量靠前写。
这条对应一类很隐蔽的问题:有人在脚本里按环境动态改content,本地看着生效(改动发生在解析之前),上线后不生效,两边代码一模一样。判断方法只有一个:看最终解析出来的那份策略,不看 DOM 里现在的content值。
指令名大小写不敏感。原话:「script-SRC ‘none’ and ScRiPt-sRc ‘none’ are equivalent」。按大小写精确匹配script-src搜配置或日志,会漏掉写成Script-Src的那些。
同一条策略里重复的指令只取第一个,其余忽略。规范有一个 Note 建议浏览器在这种情况下给控制台警告,但那只是建议。
Content-Security-Policy: script-src 'self'; img-src 'self'; script-src https://cdn.example.com⚠️代码待验证
这里script-src出现两次。第二条不合并、不覆盖,它被整条忽略。效果是「脚本只认同源」,那个 CDN 域等于没写。所以排查时要按出现顺序数,而不是找到一个匹配项就停。
6.3 同一个响应里,两个头谁说了算
最后说一处「头与头之间」的关系,理解错了会做出「多加一个更保险」这种反向操作。
the frame-ancestors directive overrides the X-Frame-Options header
覆盖。只要有 enforce 状态的frame-ancestors出现在响应里,X-Frame-Options就被忽略。不是「两个都生效取交集」,是「前者作数,后者不作数」。两者是粗略换算:'none'大致相当于DENY,'self'大致相当于SAMEORIGIN。
所以「两个都加更保险」在这里明确是错的:frame-ancestors写宽了,那个更严的X-Frame-Options不会来兜底。你以为加了两道锁,实际是把严的那道换成了宽的。
最后补一句report-uri的结论:它已被report-to取代,原话是「The report-uri directive is deprecated in favor of the new report-to directive, which relies on [REPORTING] as infrastructure」。这也属于替代关系——新的生效,旧的按废弃处理,不会叠加。
本章可以带走的一句:部署位置决定哪些指令能被读到,头与头之间是覆盖不是叠加;「多加一个更保险」在 CSP 里经常恰好相反。
七、一份可执行的自查顺序
7.1 七步,按顺序走
前面六章拆了四个误读,落到操作上需要顺序:后面的结论依赖前面的结果,不先数清有几条策略,就谈不上「哪条在拦」。
第 1 步 数清有几条策略:响应头里几条 Content-Security-Policy,页面里几个 meta 第 2 步 每条策略单独展开:把写了的 fetch 指令标出来,剩下的才归 default-src 第 3 步 每条策略单独看 'none':有没有和别的源并列,有没有空值指令 第 4 步 分清哪条是 enforce、哪条是 Report-Only:后者不计入交集 第 5 步 确认部署位置:写在头里还是 meta 里,用到的指令 meta 收不收 第 6 步 按出现顺序数重复指令:同一条策略里第一条生效,后面的忽略 第 7 步 最后再看上报:报告里 effective-directive 落在哪一条,决定改法⚠️代码待验证
七步里有三个「先别看」的位置。
第 2 步之前别看页面表现。白屏和「哪条策略在拦」之间隔着好几层:可能是交集,可能是空指令,可能是default-src被替换掉了。
第 5 步之前别改策略。策略写在meta里而你改的是frame-ancestors,改一百次也不会生效。
第 7 步之前别信报告。报告只说哪个请求被拦了,不说「这条策略是不是你要的那条」。被忽略的重复指令不产生报告,Report-Only 的报告看起来和 enforce 的一样。
7.2 四条误读并到一张表里
| 被读错的点 | 规范里的实际语义 | 自查时先做什么 |
|---|---|---|
default-src是总开关 | 无继承,写了script-src之后default-src对脚本零影响 | 第 2 步:标出每条策略里已写的 fetch 指令 |
| 多条策略取更宽的那条 | 取交集,增加策略只能收紧 | 第 1 步:数清策略条数,别只抓一次头 |
| Report-Only 是软性拦截 | disposition为 report 时在判定里被跳过,不参与交集 | 第 4 步:先分清哪条在拦 |
'none'是一种更严的写法 | 与其他源并列时无效果;空源列表等价于'none' | 第 3 步:逐条看源列表是否真的为空 |
概括成一句:四条误读共用一个形状——把「没生效的部分」当成了「生效的部分」。default-src在写了具体指令后不生效,'none'在并列时不生效,Report-Only 在拦截上不生效,重复的第二条指令不生效。你以为它在管,其实它没被问到。
7.3 这套规则以后会变吗
会。这是这一节唯一想强调的。
本文所有原文都来自 CSP3 的 2026-09-16 工作草案。它是 Working Draft,不是 Recommendation,开头说过,结尾再说一次,因为它决定这份笔记的保质期:草案条文会改,改的时候不发通知。可迁移的不是这些句子,是读的方法——先确认版本,再确认这段说的是哪个阶段(解析、判定、上报),最后确认它管哪一类请求。
规范对开发者还有一句明确建议,可以直接照做:
developers SHOULD NOT include either ‘unsafe-inline’, or data: as valid sources in their policies. Both enable XSS attacks by allowing code to be included directly in the document itself
措辞是 SHOULD NOT,建议而非禁止,但它给的是一个很硬的性质判断:这两个源会把「代码能不能进到文档」这道门整个打开。至于它们在有其他放行机制时如何被处理,本文不写——本批没核到可直接引用的原文,写出来就是替规范说话。
strict-dynamic按规范 §8.2 的描述是一种针对 non-parser-inserted 脚本元素的放行机制。这句只作概念说明:它改变的是「脚本怎么进来的」这个判据,不是「哪个域被允许」,具体行为与组合效果需要单独一篇。
本章可以带走的一句:CSP 的四条误读共用一个形状——把没被问到的部分当成了在管的部分;规则本身会随草案版本变,只有「先确认版本、再确认阶段、再确认请求类型」这个读法不会变。
完整版对照资料:这份资料把本文七章用到的那几条判定规则做成了单页检查表,从「数清几条策略」到「报告里的 effective-directive 对应哪条改法」,和第七章的七步顺序一一对应。放在资料包里,扫码即可获取:
附表 A:本文引用事实与官方出处对照表
| 序号 | 事实 | 出处 | 核验日期 | 本文位置 |
|---|---|---|---|---|
| 1 | CSP3 文档身份:W3C Working Draft,2026-09-16;是工作草案,不是 Recommendation | W3C CSP3(CSP-01) | 2026-10-25 | 一 1.1、七 7.3 |
| 2 | default-src 'self'的等价形式共 12 项,脚本与样式拆为-elem与-attr四条,无单独的script-src/style-src | W3C CSP3(CSP-02) | 2026-10-25 | 二 2.2 |
| 3 | 「There is no inheritance…the value of default-src has no influence on script requests.」 | W3C CSP3(CSP-02b) | 2026-10-25 | 二 2.1、三 3.1 |
| 4 | default-src只是 fetch 指令的兜底,不是全局开关 | W3C CSP3(CSP-03) | 2026-10-25 | 一 1.2、二 2.3 |
| 5 | prefetch/preconnect 由所有指令源列表的并集管辖;未写default-src时「will always be allowed」 | W3C CSP3(CSP-03b) | 2026-10-25 | 二 2.3 |
| 6 | 多条策略取交集:「pass through both unscathed」;增加策略「can only further restrict」 | W3C CSP3(CSP-04) | 2026-10-25 | 三 3.1 |
| 7 | 「Script will only load if it meets both policy’s criteria」 | W3C CSP3(CSP-05) | 2026-10-25 | 三 3.2 |
| 8 | disposition为 report 的策略在判定中被跳过:「then skip to the next policy」 | W3C CSP3(CSP-06) | 2026-10-25 | 四 4.1 |
| 9 | 'none'与其他源并列时无效果;« 'none', https://example.com »会匹配该域 | W3C CSP3(CSP-07) | 2026-10-25 | 五 5.1 |
| 10 | 空源列表等价于'none',不匹配任何 URL | W3C CSP3(CSP-08) | 2026-10-25 | 五 5.2 |
| 11 | 加载结果是重定向时,匹配算法忽略源表达式里的路径部分 | W3C CSP3(CSP-09) | 2026-10-25 | 五 5.3 |
| 12 | 指令名大小写不敏感;同一策略内重复指令只取第一个 | W3C CSP3(CSP-10) | 2026-10-25 | 六 6.2、七 7.1 |
| 13 | frame-ancestors覆盖X-Frame-Options;'none'约等于DENY,'self'约等于SAMEORIGIN | W3C CSP3(CSP-11) | 2026-10-25 | 六 6.3 |
| 14 | meta不支持 Report-Only,也不支持report-uri、frame-ancestors、sandbox | W3C CSP3(CSP-12) | 2026-10-25 | 六 6.1、四 4.3 |
| 15 | meta解析后再改content不生效;规范建议meta尽量靠前 | W3C CSP3(CSP-13) | 2026-10-25 | 六 6.2 |
| 16 | report-uri已被report-to取代 | W3C CSP3(CSP-14) | 2026-10-25 | 四 4.3、六 6.3 |
| 17 | 开发者 SHOULD NOT 把'unsafe-inline'或data:作为有效源 | W3C CSP3(CSP-15) | 2026-10-25 | 七 7.3 |
| 18 | 同一资源的不同表示可以带不同的策略头值 | W3C CSP3(CSP-16) | 2026-10-25 | 三 3.3 |
| 19 | 待验证/禁止:'unsafe-inline'与 nonce 的交互结论,本批未在规范中检到可引用原句,全文不写 | 本批纪律(CSP-17) | 2026-10-25 | 七 7.3 |
| 20 | 禁止:本文不写任何绕过类做法与具体白名单技巧;strict-dynamic仅作概念说明 | 本批纪律(CSP-18) | 2026-10-25 | 七 7.3 |
| 21 | default-src 'none'不覆盖frame-ancestors、base-uri、form-action、sandbox、上报指令 | W3C CSP3(CSP-03) | 2026-10-25 | 二 2.3 |
| 22 | 12 项等价清单中每一项各自管辖的请求类型 | W3C CSP3(CSP-02) | 2026-10-25 | 二 2.2 |
附表 B:术语速查表
| 术语 | 一句话解释 |
|---|---|
| fetch 指令 | 管某类请求能不能发出去的指令,只有这一类有default-src兜底 |
| 文档指令 | 管文档自身行为的指令,如base-uri、sandbox,无兜底 |
| 导航指令 | 管「谁能嵌我、我能往哪提交」的指令,如frame-ancestors、form-action,无兜底 |
| 上报指令 | 管违规信息往哪送的指令,如report-uri、report-to,无兜底 |
| 无继承 | 写了具体指令,default-src对那一类请求零影响;不是优先级低,是不参与 |
| 源列表 | 一条指令后面的那些值,内部是「或」的关系,任一项匹配即放行 |
'none' | 表示「列表为空」的关键字,任何 URL 都不匹配;与其他源并列时被吸收 |
| 空源列表 | 只写指令名、不写值,语义等价于'none',即全禁 |
| enforce | disposition为 enforce 的策略,参与拦截判定,与其他 enforce 策略取交集 |
| Report-Only | 只上报不阻断的策略,判定时被跳过,不参与交集,也不能写进meta |
script-src-elem/script-src-attr | 脚本元素与内联脚本属性两条分开的判据 |
| 覆盖 | frame-ancestors存在时X-Frame-Options被忽略;不是叠加,是后者不作数 |
写在最后:这篇用到的资料
写这篇文章时,我把 CSP3 工作草案里跟
default-src、'none'、Report-Only 有关的段落来回读了几遍,最费劲的是「多条策略取交集」和「Report-Only 不参与交集」要同时成立,顺手也整理了几份配套的东西:
- 靶场环境对照表:DVWA、upload-labs 在 Windows / macOS / Linux 三平台的可行性与推荐路径
- Web 安全学习路线图:从基础打牢到安全管理,四个阶段各学什么
- 常用靶场清单:每个靶场练什么、适合哪个阶段
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「靶场」,优先通过。
拿到之后建议先看环境对照表那一份,再回到自己站点的那几个响应头上,按第七章的七步顺序数一遍究竟有几条策略在同时生效。