一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操
作为 Web 开发工程师,我们时刻面临着各种安全威胁,其中跨站脚本攻击(XSS)是最常见也是危害最大的漏洞之一。攻击者通过注入恶意脚本,可以窃取用户敏感信息、劫持会话甚至篡改网页内容。传统的防御手段(如输入验证和输出编码)虽然重要,但往往难以做到滴水不漏。这时,浏览器提供的一项重要安全特性——Content Security Policy (CSP)——就显得尤为关键。那么今天,让我们先从理论层面好好来梳理一下 CSP,看看它到底有哪些约束来保障我们的 web 内容的安全性,下一期我们再从代码演示上来分别展示。
CSP 本质上是建立一个内容安全白名单,通过 HTTP 头或 标签告知浏览器哪些外部资源(脚本、样式、图片、字体、框架等)允许被加载和执行。如果资源的来源不在白名单内,浏览器就会拒绝加载,从而有效地阻止了大部分 XSS 攻击以及其他类型的内容注入攻击。
如下图,抖音官网的响应头中关于 CSP 相关的配置
为什么需要 CSP?
想象一下,你的网站有一个留言板功能。如果没有对用户输入进行严格的过滤,攻击者可能会提交包含恶意 标签的留言。当其他用户浏览这条留言时,浏览器会解析并执行这个恶意脚本,造成 XSS 攻击。
传统的防御: 对用户输入进行 HTML 转义,例如将 <
转换为 <
。但这依赖于开发者对所有潜在输入点的严格控制,容易遗漏。
CSP 的防御: 通过设置 CSP,你可以限制哪些域名下的脚本可以执行,甚至完全禁止页面执行内联脚本(...
)或通过 eval()
执行字符串代码。这样一来,即使攻击者成功注入了 标签,如果它的来源或执行方式不符合 CSP 策略,浏览器也会拒绝执行它,从而大大降低了 XSS 的风险。
除了 XSS,CSP 还能防御:
- 数据注入攻击: 通过限制表单提交的目标(
form-action
)。 - 点击劫持: 通过限制页面是否可以在
或
中嵌套(
frame-ancestors
)。 - 混淆内容攻击: 强制使用 HTTPS 连接(
upgrade-insecure-requests
)。
CSP 的工作原理
CSP 的核心是定义一系列指令 (Directives) 及其对应的允许来源 (Sources)。这些策略可以通过以下两种方式发送给浏览器:
-
HTTP 响应头 (推荐): 在服务器的 HTTP 响应中添加
Content-Security-Policy
或Content-Security-Policy-Report-Only
头部。Content-Security-Policy: default-src \'self\'; script-src \'self\' https://cdnjs.cloudflare.com; style-src \'self\' \'unsafe-inline\'
这是最推荐的方式,因为它可以覆盖整个页面,并且策略是在页面内容解析之前生效的。
-
标签: 在页面的
中添加一个
标签。
这种方式优先级较低,并且不能用于
report-uri
、report-to
和frame-ancestors
等指令。
Content-Security-Policy-Report-Only
头部:
这个头部用于报告违反 CSP 策略的行为,但并不阻止这些行为。这对于在生产环境中测试新的 CSP 策略非常有帮助。你可以在不影响用户体验的情况下,观察哪些资源会被新的策略阻止,然后根据报告调整策略,直到满意为止,最后再切换到 Content-Security-Policy
头部。
CSP 发展历程
浏览器兼容性
CSP 已经被主流现代浏览器广泛支持,包括 Chrome, Firefox, Safari, Edge 等。但是,不同浏览器对 CSP 规范的支持程度可能有所不同,尤其是对于一些较新或不太常用的指令(如 report-to
, worker-src
, strict-dynamic
)以及 CSP Level 2/3 的特性。
- CSP Level 1: 提供了基础的指令(如
default-src
,script-src
,style-src
,img-src
,report-uri
等)。兼容性很好。 - CSP Level 2: 增加了
base-uri
,child-src
,form-action
,frame-ancestors
,block-all-mixed-content
,upgrade-insecure-requests
以及 Nonce 和 Hash 源。兼容性也很不错,但 IE 并不完全支持。 - CSP Level 3: 增加了
worker-src
,manifest-src
,report-to
,strict-dynamic
等指令。这些指令在较新版本的浏览器中支持较好。
在部署 CSP 策略时,如果你需要支持较旧的浏览器版本时,建议参考 Can I use 网站来查询特定指令的兼容性情况。
总结兼容性要点:
- 绝大多数用户使用的现代浏览器都支持 CSP Level 1 和 Level 2 的核心特性。
- Nonce, Hash, 和
strict-dynamic
在现代浏览器中支持良好。 report-to
是相对较新的特性,兼容性不如report-uri
。frame-ancestors
只能通过 HTTP 头设置,并且在旧版 IE 中不受支持(可以使用X-Frame-Options
头作为补充防御)。
核心 CSP 指令详解
CSP 提供了非常多的指令来控制不同类型资源的加载。以下是一些最常用和重要的指令:
default-src
: 这是最重要的回退指令。如果某个资源类型没有特定的指令(例如没有script-src
),浏览器就会使用default-src
的策略。强烈建议设置此指令。script-src
: 控制 JavaScript 的来源。style-src
: 控制 CSS 样式表的来源。img-src
: 控制图片的来源。connect-src
: 控制 XMLHttpRequest (XHR), WebSockets, Fetch 等接口连接的来源。font-src
: 控制字体的来源。object-src
: 控制,
,
等标签的来源。
frame-src
: 控制,
,
等标签加载内容的来源(在新版本的 CSP 中,推荐使用
child-src
或worker-src
,但frame-src
仍被广泛支持)。child-src
: 控制 Web Workers 和其他嵌套浏览器上下文(如 frames 和 iframes)的来源。worker-src
: 控制 Worker、SharedWorker 或 ServiceWorker 脚本的来源。base-uri
: 限制标签中指定的 URL。
form-action
: 限制标签提交的目标 URL。
frame-ancestors
: 限制哪些网站可以将当前页面嵌入到,
,
,
,
中。这个指令只能通过 HTTP 头设置,不能在
标签中使用。
report-uri
: 指定一个 URL,当 CSP 策略被违反时,浏览器会发送一份违规报告到这个 URL。report-to
: 一个新的替代report-uri
的指令,支持报告分组和更详细的报告信息(需要配置 Reporting API)。upgrade-insecure-requests
: 指示用户代理将所有 HTTP URL 重写为 HTTPS。这对于有大量遗留 HTTP 资源的网站很有用。block-all-mixed-content
: 如果设置了这个指令,浏览器会阻止所有通过 HTTP 加载的资产,即使是 HTTPS 页面中的资源也会被阻止。
允许来源 (Sources) 的值:
来源可以是一个或多个,用空格分隔。常见的值包括:
-
\'self\'
: 允许来自同源(Same Origin)的资源(协议、域名、端口都相同)。 -
\\*
: 允许来自任何来源(慎用,非常不安全)。 -
domain.com
: 允许来自指定域名(及其子域名,如果前面加了*.
)的资源。 -
\\*.domain.com
: 允许来自指定域名及其所有子域名的资源。 -
https://domain.com
: 允许来自指定域名的 HTTPS 资源。 -
\'unsafe-inline\'
: **允许内联脚本 (...
) 或内联样式 (...
,style=\"...\"
)。**极不推荐使用,因为它会引入 XSS 风险。 -
\'unsafe-eval\'
: **允许使用eval()
,setTimeout(\"...\")
,setInterval(\"...\")
等从字符串执行代码的方法。**极不推荐使用,因为它也引入 XSS 风险。 -
\'nonce-\'
: 允许带有特定 nonce 属性的内联脚本或样式。服务器在每次响应时生成一个唯一的 nonce 值,并将其添加到 CSP 头和相应的或
标签上。这是一种相对安全的允许内联代码的方式,因为它需要攻击者知道当前的 nonce 值(难以猜测)。
-
- 示例 CSP 头:
script-src \'nonce-abc123xyz\'
- 示例 HTML:
...
- 示例 CSP 头:
-
\'sha256-\'
: 允许具有特定哈希值的内联脚本或样式。你可以计算内联脚本或样式的哈希值(支持 SHA256, SHA384, SHA512),并将其添加到 CSP 头中。 -
- 示例 CSP 头:
script-src \'sha256-abcdefg...\'
- 示例 HTML:
alert(\'Hello, CSP!\');
(你需要计算alert(\'Hello, CSP!\');
这段代码的 SHA256 哈希值)
- 示例 CSP 头:
-
\'strict-dynamic\'
: 一个强大的指令(通常与 nonce 或 hash 一起使用)。如果一个脚本通过 nonce 或 hash 被允许执行,那么这个脚本加载的其他脚本(例如通过document.createElement(\'script\')
创建并添加到 DOM 中)也会被信任并允许执行,而无需显式地在 CSP 中列出这些脚本的来源。这极大地简化了对复杂应用(如 SPA)的 CSP 管理,同时保持了安全性。 -
- 示例 CSP 头:
script-src \'nonce-abc123xyz\' \'strict-dynamic\'
- 示例 HTML:
const s = document.createElement(\'script\'); s.src = \'https://example.com/dynamic.js\'; document.body.appendChild(s);
(即使example.com
不在script-src
中,dynamic.js
也会被允许加载)
- 示例 CSP 头:
使用 Express 实现一个 CSP Demo
下面我们将创建一个简单的 Express 应用来演示如何设置 CSP 头部以及不同策略的效果。
项目结构:
csp-demo/├── package.json├── app.js└── public/ ├── index.html ├── safe-script.js
1. 初始化项目并安装 Express 和 body-parser (用于接收报告):
mkdir csp-democd csp-demonpm init -ynpm install express body-parser
2. 创建 public/index.html
:
CSP 示例 body { font-family: sans-serif; } console.log(\'这是一个内联脚本。\'); console.log(\'这是一个由 hash 判断的脚本。\'); 内容安全策略(CSP)演示页面
打开浏览器开发者工具查看 CSP 违规报告。

3. 创建 public/safe-script.js
:
console.log(\'这是一个来自同源的安全脚本。\');
4. 创建 app.js
:
const express = require(\'express\');const path = require(\'path\');const bodyParser = require(\'body-parser\');const app = express();const port = 3000;// 使用 body-parser 中间件来解析请求体,用于处理 CSP 报告// 针对 JSON 类型和 application/csp-report 类型的请求体进行解析app.use(bodyParser.json({ type: [\'json\', \'application/csp-report\'] // 特别用于处理 CSP 的违规报告}));// 自定义中间件:设置 Content-Security-Policy(内容安全策略)响应头app.use((req, res, next) => { // 定义你的 CSP 策略 // 当前策略规定: // - 默认资源加载来源为 \'self\'(即同源) // - 脚本允许从 \'self\' 和 cdnjs.cloudflare.com 加载 // - 样式表允许从 \'self\' 加载,并允许内联样式(\'unsafe-inline\'),仅用于演示,生产环境应避免使用 // - 图片资源允许从 \'self\' 和 https://via.placeholder.com 加载 // - 所有违反策略的行为将报告到 /report 接口 const cspPolicy = ` default-src \'self\'; script-src \'self\' https://cdnjs.cloudflare.com \'sha256-WnZRYRws9lJmeyKcnwV8cR+ycNmLoVQQPANm6GNlsUk=\'; style-src \'self\' \'unsafe-inline\'; img-src \'self\' https://placehold.co; report-uri /report; `.replace(/\\s+/g, \' \').trim(); // 清理多余的空白字符 // 使用 Content-Security-Policy 响应头来 **强制执行** 策略 res.setHeader(\'Content-Security-Policy\', cspPolicy); next();});// 从 \'public\' 目录提供静态文件(HTML、CSS、JS 等)app.use(express.static(path.join(__dirname, \'public\')));// 接收 CSP 违规报告的接口app.post(\'/report\', (req, res) => { console.log(\'收到 CSP 违规报告:\'); console.log(JSON.stringify(req.body, null, 2)); // 将报告内容格式化后打印到控制台 res.sendStatus(204); // 返回 204 No Content 表示成功接收但无内容返回});// 启动服务器app.listen(port, () => { console.log(`CSP 示例应用正在运行在 http://localhost:${port}`); console.log(\'请在浏览器中打开 http://localhost:3000 并查看控制台输出。\');});
5. 运行应用:
node app.js
打开浏览器访问 http://localhost:3000
。打开开发者工具的 Console (控制台) 和 Network (网络) 面板。
观察现象:
-
根据
script-src \'self\' https://cdnjs.cloudflare.com;
策略: -
- 内联脚本(“这是一个内联脚本.”)会被阻止,除非策略中包含
\'unsafe-inline\'
或使用了 nonce/hash。 - 同源脚本 (
safe-script.js
) 会被允许加载。 - 来自 cdnjs 的 jQuery 脚本会被允许加载。
- console.log(‘这是一个由 hash 判断的脚本。’); 会被允许加载
- 内联脚本(“这是一个内联脚本.”)会被阻止,除非策略中包含
-
根据
style-src \'self\' \'unsafe-inline\';
策略: -
- 内联样式 (
body { ... }
) 会被允许,因为包含了\'unsafe-inline\'
。如果去掉\'unsafe-inline\'
,内联样式就会被阻止。
- 内联样式 (
-
根据
img-src \'self\' https://via.placeholder.com;
策略: -
- 来自
https://placehold.co
的图片会被允许加载。如果去掉这个来源,图片就会被阻止。
- 来自
-
内联事件处理函数 (
onclick=\"alert(\'...\')
) 通常会被script-src
阻止,即使有\'unsafe-inline\'
。它们需要更宽松的策略或使用其他技术(如通过 JavaScript 添加事件监听器)。 -
在 Console 中,你会看到关于 CSP 违规的警告或错误信息。
- 如果你的策略包含了
report-uri /report;
并且有违规发生,服务器端的控制台会打印出接收到的违规报告的 JSON 数据。
你可以修改 app.js
中的 cspPolicy
来实验不同的 CSP 策略,观察它们对页面行为的影响。例如,去掉 \'unsafe-inline\'
,看看内联脚本和样式是否被阻止。
构建工具 (Webpack/Vite) 的最佳实践
现代 Web 开发通常使用构建工具(如 Webpack 或 Vite)来打包、优化和处理资源。构建过程可能会产生一些 CSP 需要特别关注的问题:
- 内联代码: 构建工具(尤其是 Webpack 的一些插件)可能会生成一些内联的运行时脚本或小的 CSS 块。这些内联代码如果没有被 CSP 允许,会导致应用无法正常运行。
- 哈希文件名: 构建工具常常为了缓存优化,给输出文件添加哈希(例如
bundle.abcdef.js
)。CSP 策略通常是基于来源路径,这与哈希文件名不冲突。
解决方案:
-
最小化内联代码: 尽量减少构建工具生成的内联脚本和样式。
-
使用 Nonce 或 Hash:
-
- 实现: 构建完成后,计算所有内联脚本和样式的哈希值(SHA256, SHA384 或 SHA512)。将这些哈希值添加到 CSP 响应头的
script-src
和/或style-src
指令中,例如script-src \'sha256-HASH_OF_SCRIPT1\' \'sha256-HASH_OF_SCRIPT2\' \'self\' ...;
。 - 这种方法对于每次构建都会变化的内联代码(如包含构建时间戳的代码)不太实用。
- 实现: 构建完成后,计算所有内联脚本和样式的哈希值(SHA256, SHA384 或 SHA512)。将这些哈希值添加到 CSP 响应头的
-
- 实现: 服务器在每个请求时生成一个唯一的 base64 编码的 Nonce 值。将这个 Nonce 值添加到 CSP 响应头的
script-src
和/或style-src
指令中,例如script-src \'nonce-YOUR_NONCE_VALUE\' \'self\' ...;
。 - 同时,将这个 Nonce 值传递给你的前端模板引擎,将它作为
nonce
属性添加到构建工具生成的内联或
标签上。
- 一些构建工具或其插件提供了 Nonce 集成的能力。例如,在使用 HTMLWebpackPlugin 生成 HTML 时,可以通过插件选项将 Nonce 注入到生成的标签中。
- 实现: 服务器在每个请求时生成一个唯一的 base64 编码的 Nonce 值。将这个 Nonce 值添加到 CSP 响应头的
-
- Nonce: 这是处理动态生成的内联脚本(如运行时代码、动态导入的启动脚本)的最佳方式。
- Hash: 适用于内容不变的静态内联脚本或样式。
-
使用
\'strict-dynamic\'
(与 Nonce/Hash 配合): 当使用nonce
或hash
允许了初始脚本后,可以使用\'strict-dynamic\'
允许这些被信任的脚本动态加载其他脚本,而无需在 CSP 中明确列出所有可能的来源。这极大地简化了 SPA 等应用的 CSP 配置。 -
- 示例策略:
script-src \'nonce-YOUR_NONCE_VALUE\' \'strict-dynamic\' \'self\' https:; object-src \'none\'; base-uri \'self\';
- 注意:当
\'strict-dynamic\'
生效时,会忽略\'unsafe-inline\'
和基于 URL 的白名单(例如https://cdn.example.com
),除非它们也带有 Nonce 或 Hash 或通过被信任的脚本加载。但\'self\'
通常仍然有效作为回退。
- 示例策略:
-
配置允许的来源: 对于外部引用的资源(JS, CSS, Images 等),根据你的项目依赖,在 CSP 策略中明确列出所有允许的 CDN、API 服务等来源。
实施 CSP 的建议步骤
- 分析现有应用: 了解你的应用加载了哪些外部资源,哪些使用了内联脚本或样式,哪些使用了
eval()
。 - 从
report-only
开始: 在生产环境中使用Content-Security-Policy-Report-Only
头部和report-uri
指令。部署策略,并收集违规报告,分析哪些合法的资源被阻止了。 - 逐步收紧策略: 根据报告调整策略,逐渐移除
\'unsafe-inline\'
和\'unsafe-eval\'
,明确列出允许的来源。优先解决script-src
和default-src
的问题。 - 考虑 Nonce 或 Hash: 如果确实需要内联脚本(如构建工具生成的运行时代码),集成 Nonce 或 Hash 机制。考虑使用
\'strict-dynamic\'
简化管理。 - 切换到强制模式: 当你确信策略不会阻止应用的正常功能时,将
Content-Security-Policy-Report-Only
切换为Content-Security-Policy
。 - 持续监控: 即使在强制模式下,也要继续使用
report-uri
或report-to
收集报告,以便及时发现新的问题或潜在的攻击尝试。 - 结合其他安全措施: CSP 是重要的安全层,但不能替代输入验证、输出编码、HTTP Strict Transport Security (HSTS)、SameSite cookies 等其他安全实践。
总结
Content Security Policy (CSP) 是现代 Web 安全中不可或缺的一部分,它是防御 XSS 和其他内容注入攻击的强大武器。通过明确声明页面允许加载和执行的资源来源,CSP 为浏览器提供了一个安全基线,即使其他防御措施失效,也能在一定程度上限制攻击的危害范围。
虽然部署 CSP 需要一些工作来分析现有应用并调整策略,但这项投入是值得的。从 report-only
模式开始,逐步收紧策略,结合 Nonce 或 Hash 处理内联代码,并利用 \'strict-dynamic\'
简化复杂应用的策略管理,你就能有效地提升你的 Web 应用的安全性,为你的用户提供更安全的浏览体验。
如何系统学习网络安全?
网络安全作为数字时代的核心技术基石,已成为保障各行业安全稳定运行的坚实屏障。筑牢网络安全的防线,掌握先进的防护策略和技能正上升为国家与企业发展的战略优先级。
构建稳固的网络安全能力是一个体系的工程,需要从夯实基础理论出发,持续深化到攻防对抗与应急响应的实战层面。
如果你是准备学习网络安全(黑客)或者正在学习,这里给你准备了腾讯大佬内部分享的资料,你应该能用得上:
①网络安全学习路线
②20份渗透测试电子书
③安全攻防357页笔记
④50份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年CTF夺旗赛题解析
一、网络安全(黑客)学习路线
网络安全(黑客)学习路线,形成网络安全领域所有的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
二、网络安全教程视频
我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。
三、网络安全CTF实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这里带来的是CTF&SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~
四、网络安全面试题
最后,我们所有的作为都是为就业服务的,所以关键的临门一脚就是咱们的面试题内容,所以面试题板块是咱们不可或缺的部分,这里我给大家准备的就是我在面试期间准备的资料。
网安其实不难,难的是坚持和相信自己,我的经验是既然已经选定网安你就要相信它,相信它能成为你日后进阶的高效渠道,这样自己才会更有信念去学习,才能在碰到困难的时候坚持下去。
机会属于有准备的人,这是一个实力的时代。人和人之间的差距不在于智商,而在于如何利用业余时间,只要你想学习,什么时候开始都不晚,不要担心这担心那,你只需努力,剩下的交给时间!
这份完整版的网络安全学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】