
授权与合规声明本文全部操作对象均为自建隔离靶场本机容器或隔离虚拟机涉及安全测试的环节必须以取得合法授权为前提。未经授权的渗透测试违反《中华人民共和国网络安全法》与《刑法》相关条款须承担相应法律责任。本文只讲环境配置、版本对照与靶场隔离不含任何攻击步骤、利用载荷与绕过手法请勿将文中环境指向任何非自有系统。一、为什么会有响应头里的安全开关这一层1.1 请求是客户端发的响应头是服务端写的一个 HTTP 事务可以粗分成两半请求是客户端发给服务端的响应是服务端发回客户端的。请求行和请求头由客户端写状态行和响应头由服务端写。很多人把 Web 安全的注意力全放在浏览器那一侧觉得安全是浏览器管的。浏览器确实是执行者但相当一部分约束并不是浏览器自己拍脑袋决定的而是服务端在响应里声明、浏览器照着执行。这一层就是响应头里的安全开关。一次只取响应头的请求看到的就是这一层的全部内容。⚠️代码待验证curl-Ihttp://127.0.0.1:8080/这条命令只取头不取正文-I即--head台账 C05。输出的每一行都是服务端写的客户端一行都改不了。理解这一点很关键这些开关的开关权在服务端不在浏览器也不在使用者。1.2 三个开关都在响应侧本文只讲三个它们恰好覆盖三个完全不同的层面开关出现在哪一句话职责Content-Security-PolicyCSP响应头也可经 meta声明页面能加载和执行什么Strict-Transport-SecurityHSTS响应头声明连接只能走安全传输X-Content-Type-Options: nosniff响应头声明响应体的类型不许被猜三个开关的共同点只有两条名字都落在响应头里都由服务端声明、由浏览器执行。差别远大于共同点——一个管内容一个管连接一个管类型判定。常见误解就是把它们当成同一类安全加固项打包理解结果每次说不清它到底管什么。顺带记一下三种典型的错位把 CSP 当成防注入的总闸、把 HSTS 当成页面安全、把nosniff当成内容鉴定。这三句话的错误是同一个——把开关的管辖范围放大了。后面每一章做的事其实就是把这个范围重新量一遍。1.3 本文只讲语义不给配置不讲绕过动手之前先把三条边界说清楚只讲机制语义与边界每个开关管什么、明确不管什么。不提供任何服务端配置写法不给 Nginx/Apache 片段也不给完整策略串。不讲任何绕过、规避、探测手法。本文是防御侧视角。自查只给只读动作用命令把响应头取回来看仅此而已。本章可以带走的一句三个安全开关都是服务端声明、浏览器执行理解它们的正确顺序是——先看它管哪一层再看它不管哪一层。二、CSP 是什么2.1 一句话定位锁定、缓解、降权W3C 在《Content Security Policy Level 3》里对它的用途给了一段很精炼的表述开发者用它来以多种方式锁定lock down应用缓解内容注入类漏洞如跨站脚本的风险并降低应用执行时的权限台账 P03。三个动词对应三件事不要混锁定给什么能加载、什么能执行设限。这是 CSP 的主干。缓解注意用词是缓解mitigate“不是消除”。注入类漏洞的根因不在这里解决。降权即使页面已经被注入了内容CSP 也尽量让这段内容能做的事变少。这正是它作为纵深防御的价值所在。把这三个动词放进同一句话里CSP 的目标不是让注入进不来而是即使注入进来了它能做的事也被压缩。它的发力点在后半段——不是堵住入口而是抬高出口的门槛。2.2 从目标清单里读出细到什么程度规范在目标一节列得比较细台账 P04。它要给可请求并进而嵌入或执行的资源“内联脚本的执行”“动态代码执行eval()及类似构造”内联样式的应用这四类提供较细粒度的控制同时提供一套策略框架用于降低应用权限还提供一个报告机制用来发现被利用的缺陷。这里要克制两件事报告机制只写到存在这一层。CSP 确实提供了报告机制这是它的目标之一至于报告怎么投递、相关字段怎么用本文不展开。四类控制只读到有细粒度。它们分别对应哪些指令、怎么写属于配置层不在本文范围。值得注意的一个细节是这四类里有两类内联脚本、eval()这类动态执行都指向同一个传统上很难对付的问题——一堆看起来像字符串的东西最后被当成代码执行了。CSP 的思路不是阻止字符串进来而是让这段字符串即使进来了也执行不了——这正好呼应了 2.1 里抬高出口门槛的说法。换个角度看这份清单它其实只回答了一个问题在资源被请求、被嵌入、被执行、被应用这条链上每一环有没有独立可调的开关。有的环节开关多、颗粒细有的环节只能整体收紧。把这条链想清楚后面读default-src就不容易迷路。2.3 版本锚点本文引用的规范原文来自 W3C《Content Security Policy Level 3》。本次核对到的版本为W3C Working Draftthis version https://www.w3.org/TR/2026/WD-CSP3-20260916/截至 2026-10-07的最新 WD台账 P01。留意它是Working Draft不是已冻结的标准——这意味着细节仍可能变动读的时候要认版本号。本章可以带走的一句CSP 的三个动词是锁定、缓解、降权——缓解不是消除降权才是它对付注入时最扎实的那一手。三、CSP 的 default-src 为什么重要3.1 它是兜底不是某一个开关规范里写得非常直白逐字台账 P05The default-src directive serves as a fallback for the other fetch directives.逐字读default-src是其他 fetch 指令的兜底。也就是说它的地位和别的指令不一样——别的指令管一类请求它管没被别的指令管到的那些。若策略里存在default-src它的取值就成为该策略的默认来源列表。初学者最容易在这里翻车以为写了default-src就等于写了全部限制。其实它只在没有更具体的指令可用时生效一旦某类请求有专门指令专门指令优先default-src让位。换个角度说如果你只写了一条很具体的指令比如只限制脚本来源那么其余所有类别都会落进default-src的手里。所以策略到底宽不宽常常不取决于你写了什么而取决于default-src兜成了什么。写得很细的指令只说明了这一类我管了说明不了别的类别也收紧了。3.2 官方那个例子怎么读规范给了一个例子来说明这个兜底关系对default-src none; script-src self这样一条策略脚本请求用self匹配其他请求用none台账 P05。⚠️ 这里必须把话说死上面这串是规范里的示例用于说明兜底/让位的语义本文不提供配置方法也不要照抄到任何地方。我们只从它读出两件事有专门指令时专门指令生效——所以脚本走self而不是被none一票否决。没有专门指令时default-src接管——其他所有请求都被none兜住。这正是default-src的价值它决定了那些你没想起来管的请求默认是放开还是收起。策略写不细的时候一个收紧的default-src是唯一的保险。3.3 一个例外资源提示不归它直接管有一个细节值得单独记prefetch/preconnect这类资源提示所产生的请求不隶属于某个具体的 fetch directive而是由策略中全部指令来源列表的并集来管辖并且若未指定default-src这类请求将始终被允许台账 P06。这段话有两层意思这类请求没有专门的管家所以它的判定规则和别人不一样是所有来源列表合并起来看。一旦策略里连default-src都没有那对这类请求就等于没有任何约束——它会一直放行。也就是说default-src在这里不只兜底没写指令的那些类别还在实际上决定了这类无主请求有没有一道基线。这解释了为什么规范把它单列为 fallback它是策略的下限。本章可以带走的一句default-src管的是没被其他指令管到的请求默认放开还是收起没有它策略就没有下限。四、策略怎么送到浏览器4.1 两种投递方式响应头与 meta一条 CSP 策略怎么从服务端走到浏览器规范给了两种投递方式台账 P07一是HTTP 响应头二是 HTML 里的meta元素http-equiv。这两种方式不是二选一的关系。规范明确说通过 meta 投递的策略会与经头字段投递的策略一并执行——两条策略同时生效而不是后者覆盖前者。规范还强烈建议作者应把 meta 元素放在文档尽量靠前的位置。原因不难理解策略要在内容开始被处理之前就位越早越好。4.2 meta 有它的限制但 meta 并不是万能的投递通道。规范指出不是所有指令都能在 meta 元素里使用原文措辞为 “not supported inside a meta element”report-uri即属于此类台账 P08。本文不逐个列举哪些指令不能进 meta——这一点本次只核到存在这类限制以及report-uri属此类没有逐条核全因此从严处理只写存在限制这一层不展开名单。把 4.1 和 4.2 合起来看结论很干净投递方式是两种但两种通道的通行能力并不对称。写策略的人如果只记得meta 也能发策略很容易踩到某条指令在 meta 里根本不生效的坑。还有一层容易被忽略的差别响应头是随每一次响应走的meta 是随文档走的。同一台服务器上不同路径可能由不同程序生成因此同一站点不同响应返回的响应头可能并不一样而 meta 写在文档里只对那份文档生效。所以这个站点有没有策略这种问法本身就不够精确——得落到具体的某一次响应上这也正是下一章要换个层面去看的原因。本章可以带走的一句策略有响应头和 meta 两条投递通道两条会同时生效但 meta 装不下所有指令。五、HSTS 管的是连接这一层5.1 它声明的是只能走安全连接HSTS 的定义一种机制使网站可以声明自己只能通过安全连接访问并/或让用户代理只通过安全连接与指定站点交互该策略通过Strict-Transport-SecurityHTTP 响应头字段以及用户代理配置等其他方式来声明台账 H01。它的语义有一句非常硬的表述该头字段向 UA 表明UA MUST 对该响应所属主机强制执行 HSTS 策略台账 H02。注意该响应所属主机这个限定——策略的作用对象是主机不是某一条 URL。这就是它和 CSP 的根本差别CSP 管的是传输完成之后页面里的内容怎么跑。HSTS 管的是传输本身连接走不走安全通道。一个在内容层一个在连接层。也正因为它在连接层观察它的方式跟观察 CSP 不一样——必须从一个安全连接上取头⚠️代码待验证curl-Ihttps://127.0.0.1:8443/注意地址用的是https://。若改用http://去取即便服务端真的发了这个头按规范它也是无效的——这一条在 5.3 会再展开。5.2 语法与四条总体要求规范给出的 ABNF 如下逐字台账 H03Strict-Transport-Security Strict-Transport-Security : [ directive ] *( ; [ directive ] )directive directive-name [ directive-value ]围绕这条语法规范还有四条总体要求台账 H04指令的出现顺序不重要所有指令在一个头字段里 MUST 只出现一次——重复出现不合规指令名大小写不敏感UA MUST 忽略任何含不符合本节语法的指令或取值的该头字段——注意是整条作废不是只丢掉那一段。第 4 条最容易被低估。它不是宽容解析只要有一个指令或取值不符合语法整个头字段都被忽略等于这次声明白写。还有一条必须写死的限定台账 H07max-age与includeSubDomains这两个指令名规范在给语法示例时用的是小写写法而指令名大小写不敏感见第 3 条。所以不得写成指令名必须小写——小写只是示例的写法不是要求。5.3 三条必须记住的处理规则max-age是必需的。它是 REQUIRED 指令给出的是从接收到该头字段起UA 把这个主机视为 Known HSTS Host 的秒数。而max-age0表示 UA 不再把该主机当作 HSTS Host也就是撤销并且此时includeSubDomains的存在被忽略台账 H05。includeSubDomains是可选的。它是 OPTIONAL 的无值指令作用是把策略扩展到该主机的子域台账 H06。指令是否必需取值作用max-ageREQUIRED秒数该主机被视为 Known HSTS Host 的时长0表示撤销includeSubDomainsOPTIONAL无值把策略扩展到该主机的子域最重要的一条处理规则逐字台账 H08If an HTTP response is received over insecure transport, the UA MUST ignore any present STS header field(s).逐字读若 HTTP 响应经非安全传输收到UA 必须忽略其中出现的该头字段。这一条把 HSTS 的一个先天限制摆在了明面上——它没法保护第一次。第一次请求若走的是非安全传输这个头会被直接忽略。所以 HSTS 保护的是已经被安全地告知过之后的访问。另外一次响应中若出现多个该头字段且响应经安全传输收到UAMUST 只处理第一个台账 H09。⚠️ 与 HSTS 相关的两件事本文一律不写一是预加载列表相关内容本批未核到一手规范见附表 B二是 HSTS 与混合内容的交互RFC 6797 未讨论且该交互属另一篇的题目。完整版环境对照表想知道自己搭起来的环境到底把哪些响应头送回来了前提是先有一份能稳定返回响应头的自建环境。这份对照表把常见靶场在本机不同平台下的可行路径列在一起和本章这一层正好接上。放在资料包里扫码即可获取本章可以带走的一句HSTS 管的是连接层max-age是它唯一必需的部分而它对非安全传输收到的那一次响应完全不管。六、X-Content-Type-Options: nosniff 只管两件事6.1 它做的动作是比对这个响应头的作用规范首句说得很清楚要求把响应的Content-Type与请求的目的地destination进行比对台账 N01。关键词是比对——它不是检查内容真实类型也不是给响应体做鉴定。它做的是把服务端声称的类型和这次请求打算拿它干什么放在一起看看两者是否匹配。理解这一点就不会对它抱有不切实际的期待它不负责内容是不是伪装过的它负责声明与该用途是否对得上。6.2 取值只有一种写法取值 ABNF 如下逐字台账 N02X-Content-Type-Options nosniff ; case-insensitive判定算法同样明确取该头字段的取值列表若为 null 则返回 false若第一个取值与nosniff做ASCII 大小写不敏感匹配则返回 true否则返回 false。注意两点只有第一个取值参与判定匹配是大小写不敏感的。6.3 它只管两件事这一节是全文最需要记住的部分。规范写得很窄台账 N03仅当请求目的地为script-like时若 MIME 类型解析失败或不是 JavaScript MIME 类型则阻断仅当目的地为style时若 MIME 类型解析失败或其 essence不是text/css则阻断其余目的地一律返回 allowed。请求目的地判定结果script-likeMIME 不是 JavaScript 类型或解析失败阻断styleessence 不是text/css或解析失败阻断其他任何目的地不参与判定一律 allowed规范还给了范围说明逐字台账 N04Only request destinations that are script-like or “style” are considered as any exploits pertain to them.逐字读只有 script-like 与style这两类请求目的地被纳入考虑。规范并补充说明曾考虑image但与已部署内容不兼容原文“considering “image” was not compatible with deployed content”。把这条规则落到具体场景上只有打算当脚本执行或打算当样式表用的资源才可能被这个头阻断。取一个脚本资源的头来看⚠️代码待验证curl-Ihttp://127.0.0.1:8080/static/app.js对照着读如果这个响应带了X-Content-Type-Options: nosniff而它的Content-Type又不是 JavaScript 类型那么按规范就该阻断把同一个头挂到一张图片上图片不会因此被挡——因为image根本不在判定范围里。所以它明确不管的恰恰是最容易被想当然的那一大片图片、字体、媒体、文档等等都不在它的判定范围里。把nosniff讲成挡住所有 MIME 混淆是把只覆盖两类讲成了覆盖全部方向就错了。本章可以带走的一句nosniff的判定范围只有 script-like 与style两类目的地其余一律放行——它不覆盖图片也不覆盖任何其他类型。七、三个开关的边界以及怎么只读地自查7.1 先把 CSP 的定位原样念一遍规范对它自己的定位有一段话值得逐字读台账 P02CSP is not intended as a first line of defense against content injection vulnerabilities. Instead, CSP is best used as defense-in-depth. It reduces the harm that a malicious injection can cause, but it is not a replacement for careful input validation and output encoding.逐字读CSP 不打算作为对抗内容注入类漏洞的第一道防线它最适合的角色是纵深防御它降低的是恶意注入所能造成的危害但它不是认真的输入校验与输出编码的替代品。这段话把 CSP 的位置钉死了它是第几层的问题不是有没有的问题。把它当成第一道防线就是位置错了。7.2 三个开关的边界对照把这一章之前的所有内容压成一张表开关管什么明确不管什么CSP页面能加载/嵌入/执行哪些资源内联脚本、动态执行、内联样式提供策略框架与报告机制存在这一层不是对抗注入的第一道防线不替代输入校验与输出编码meta投递时部分指令不可用HSTS该主机的连接只能走安全传输max-age必需includeSubDomains可选不管首次非安全传输收到的响应该头被忽略不管页面内容本身nosniff比对Content-Type与请求目的地只对 script-like 与style两类阻断不判定图片等其他任何目的地不做内容真实性鉴定下面三个说法方向都是错的本文写死错误表述为什么错“配好 CSP 就不会被 XSS”规范原文说 CSP 不是第一道防线、不能替代输入校验与输出编码P02它只降低危害“加了 nosniff 就挡住所有 MIME 混淆”规范把范围限定为 script-like 与style两类其余一律 allowedN03/N04“上了 HSTS 就一劳永逸”HSTS 只约束该主机的连接走安全传输非安全传输收到时该头整个被忽略H08且它完全不涉及页面内容7.3 只读自查把响应头取回来看要判断这三个开关在这台服务器上到底有没有、写了什么最省事的办法是只读地把响应头取回来逐行看。以下动作全部是只读的不改动任何东西也不提供任何服务端配置写法。只取头逐行读⚠️代码待验证curl-Ihttp://127.0.0.1:8080/想把响应头和正文放在一起看正文也一并输出便于对照内容与声明⚠️代码待验证curl-s-ihttp://127.0.0.1:8080/index.html-i即--show-headers把头输出到结果里与数据同一路输出台账 C04。如果你只关心头、不想要正文用-I台账 C05就够了若请求会经历重定向-L会让 curl 对新位置重发请求此时配合--head或--show-headers所有被请求页面的头都会显示台账 C03——这一条在同一站点上不同响应返回不同安全头时特别有用。看头的时候按顺序问四个问题有没有这三个开关的头在不在HSTS 是非安全传输下必然缺席的所以在http://上看不到它不要惊讶。值是什么HSTS 的max-age给了多少秒有没有includeSubDomains记住max-age0等于撤销。语法是否成立HSTS 头里若有不符合语法的指令或取值整条会被忽略H04——“有不等于生效”。范围对不对nosniff只管 script-like 与styleCSP 的兜底看default-src在不在。把这几条串成一次最小动作先用curl -I看有没有再用curl -s -i把头和正文对着看值是什么最后判断有是不是等于生效。整个过程不改动任何状态也不需要写任何一行服务端配置。⚠️ 提醒一句curl -I发出的是HEAD请求取回的是该文档的头。不同 URL、不同方法返回的头可能不同所以只在首页看过一遍不足以给整个站点下结论。完整版环境对照表本章的自查动作全靠把响应头取回来逐行看前提依然是有一份能返回真实响应头的自建环境。这份对照表和本文的三条边界正好互补——它解决在哪看本文解决看到后怎么读。放在资料包里扫码即可获取本章可以带走的一句三个开关的边界各不相同——CSP 是纵深防御而非第一道防线HSTS 从不管首次非安全传输nosniff只管两类目的地自查只需一条只读命令把响应头取回来看。附表 A本文引用事实与官方出处对照表事实出处本文位置P01 CSP3 本次为 W3C Working Draftthis version TR/2026/WD-CSP3-20260916/截至 2026-10-07W3C《Content Security Policy Level 3》TR 页头2.3P02 CSP 不是对抗内容注入的第一道防线最适合做纵深防御不替代输入校验与输出编码CSP3 §1 Introduction7.1P03 CSP 用途锁定应用、缓解内容注入类漏洞如跨站脚本风险、降低应用执行权限CSP3 §12.1P04 CSP 目标四类细粒度控制、策略框架、报告机制CSP3 §1.2 Goals2.2P05 “The default-src directive serves as a fallback for the other fetch directives.”例default-src none; script-src self脚本走self、其他走noneCSP3 §6.1.33.1 / 3.2P06 prefetch/preconnect 类资源提示请求不隶属具体 fetch directive由全部指令来源列表并集管辖未指定default-src时始终允许CSP3 §6.1.33.3P07 两种投递方式HTTP 响应头与 meta 元素两条策略一并执行建议 meta 放在文档靠前位置CSP3 §2 Policy Delivery4.1P08 不是所有指令都能在 meta 中使用“not supported inside a meta element”report-uri属此类CSP3 §24.2H01 HSTS 定义声明只能通过安全连接访问并/或让 UA 只通过安全连接交互经Strict-Transport-Security头声明RFC 6797 Abstract5.1H02 该头向 UA 表明 UA MUST 对该响应所属主机强制执行 HSTS 策略RFC 6797 §6.15.1H03 ABNF逐字RFC 6797 §6.15.2H04 四条总体要求顺序不重要 / 每指令只出现一次 / 指令名大小写不敏感 / 含不合语法内容的该头字段整体忽略RFC 6797 §6.15.2H05max-age为 REQUIRED给出秒数max-age0表示撤销此时includeSubDomains的存在被忽略RFC 6797 §6.1.1 / §6.1.25.3H06includeSubDomains为 OPTIONAL 无值指令把策略扩展到子域RFC 6797 §6.1.25.3H07 语法示例用小写但指令名大小写不敏感——不得写成必须小写RFC 6797 §6.1 / §6.1.25.2H08 “If an HTTP response is received over insecure transport, the UA MUST ignore any present STS header field(s).”RFC 6797 §7.25.3H09 一次响应出现多个该头且经安全传输时UA MUST 只处理第一个RFC 6797 §7.25.3N01 该响应头要求把响应的Content-Type与请求的目的地比对WHATWG Fetch §3.66.1N02 ABNFX-Content-Type-Options nosniff ; case-insensitive取第一个取值做 ASCII 大小写不敏感匹配WHATWG Fetch §3.66.2N03 仅 script-like 且非 JavaScript MIME 时阻断仅style且 essence 非text/css时阻断其余一律 allowedWHATWG Fetch §3.6.16.3N04 “Only request destinations that are script-like or “style” are considered…”曾考虑image但与已部署内容不兼容WHATWG Fetch §3.6.1 末6.3C03-L使 curl 对新位置重发请求与--head/--show-headers同用时显示所有被请求页面的头curl 官方 man page7.3C04-i / --show-headers把响应头输出到结果里curl 官方 man page7.3C05-I / --head只取头curl 官方 man page1.1 / 7.3附表 B术语速查表术语本文口径CSP内容安全策略经响应头或 meta 投递的策略控制页面能加载/嵌入/执行什么定位是纵深防御不是第一道防线default-srcCSP 中其他 fetch 指令的兜底未指定时资源提示类请求将始终被允许meta 投递CSP 的第二种投递通道与头字段投递的策略一并执行部分指令在 meta 中不可用HSTS让网站声明只能经安全连接访问经Strict-Transport-Security头声明作用对象是主机max-ageHSTS 唯一必需指令单位秒max-age0表示撤销此时includeSubDomains的存在被忽略includeSubDomainsHSTS 可选无值指令把策略扩展到子域指令名大小写不敏感不是必须小写nosniffX-Content-Type-Options的唯一取值只对 script-like 与style两类目的地做阻断判定script-like请求目的地的类别之一nosniff判定范围的两类之一另一类是styleKnown HSTS HostUA 在max-age时长内视为已强制 HSTS 策略的主机待验证HSTS 的预加载列表本批未核到一手规范CSP 中不可用于 meta 的完整指令名单本批仅核到存在此类限制与report-uri属此类CSP 报告机制的完整语义本批仅核到存在报告机制这一层写在最后这篇用到的资料写这篇文章时三个安全开关我特意只写管什么、不管什么因为大多数人卡住的不是没听过 CSP / HSTS / nosniff而是说不清它们的边界在哪。读响应头这件事缺的往往不是命令而是一份能稳定返回响应头的环境和一套看到后怎么读的顺序。顺手也整理了几份配套的东西靶场环境对照表DVWA、upload-labs 在 Windows / macOS / Linux 三平台的可行性与推荐路径Web 安全学习路线图从基础打牢到安全管理四个阶段各学什么常用靶场清单每个靶场练什么、适合哪个阶段资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「靶场」优先通过。拿到之后建议先看环境对照表那一份把环境跑起来、用一条只读命令把响应头取回来再对照本文那张管什么/不管什么的表逐行读一遍印象会比死记名词牢得多。