☰
加了 CSP 还是没拦住:四条被读错的指令语义
2026/10/25 21:12:25 网站建设 项目流程

授权与合规声明
本文全部操作对象均为自建隔离靶场(本机容器或隔离虚拟机),涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款,须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离,不含任何攻击步骤、利用载荷与绕过手法,请勿将文中环境指向任何非自有系统。

一、先分清 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 项:

序号等价清单里的指令它管的是哪一类请求
1connect-src脚本发起的连接(XHR、WebSocket 这一类)
2font-src字体文件
3frame-src这一页里嵌进去的子框架
4img-src图片
5manifest-srcWeb App Manifest
6media-src音频与视频
7object-src插件类对象
8script-src-elem脚本元素
9script-src-attr元素上的内联脚本属性
10style-src-elem样式元素
11style-src-attr元素上的内联样式属性
12worker-srcWorker 类脚本

有一个必须原样保留、不能「顺手改对」的细节:清单里是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-PolicyContent-Security-Policy-Report-Only
处置(disposition)enforcereport
是否参与拦截判定参与不参与,判定时被跳过
是否阻断请求会阻断不阻断
是否与其他策略取交集会不会
能否写在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:本文引用事实与官方出处对照表

序号事实出处核验日期本文位置
1CSP3 文档身份:W3C Working Draft,2026-09-16;是工作草案,不是 RecommendationW3C CSP3(CSP-01)2026-10-25一 1.1、七 7.3
2default-src 'self'的等价形式共 12 项,脚本与样式拆为-elem与-attr四条,无单独的script-src/style-srcW3C 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
4default-src只是 fetch 指令的兜底,不是全局开关W3C CSP3(CSP-03)2026-10-25一 1.2、二 2.3
5prefetch/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
8disposition为 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',不匹配任何 URLW3C 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
13frame-ancestors覆盖X-Frame-Options;'none'约等于DENY,'self'约等于SAMEORIGINW3C CSP3(CSP-11)2026-10-25六 6.3
14meta不支持 Report-Only,也不支持report-uri、frame-ancestors、sandboxW3C CSP3(CSP-12)2026-10-25六 6.1、四 4.3
15meta解析后再改content不生效;规范建议meta尽量靠前W3C CSP3(CSP-13)2026-10-25六 6.2
16report-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
21default-src 'none'不覆盖frame-ancestors、base-uri、form-action、sandbox、上报指令W3C CSP3(CSP-03)2026-10-25二 2.3
2212 项等价清单中每一项各自管辖的请求类型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',即全禁
enforcedisposition为 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 安全学习路线图:从基础打牢到安全管理,四个阶段各学什么
  • 常用靶场清单:每个靶场练什么、适合哪个阶段

资料是我自己整理的,放在下面这个码上,扫码即可获取:

添加时备注「靶场」,优先通过。

拿到之后建议先看环境对照表那一份,再回到自己站点的那几个响应头上,按第七章的七步顺序数一遍究竟有几条策略在同时生效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询