> 技术文档 > 一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操

一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操

作为 Web 开发工程师,我们时刻面临着各种安全威胁,其中跨站脚本攻击(XSS)是最常见也是危害最大的漏洞之一。攻击者通过注入恶意脚本,可以窃取用户敏感信息、劫持会话甚至篡改网页内容。传统的防御手段(如输入验证和输出编码)虽然重要,但往往难以做到滴水不漏。这时,浏览器提供的一项重要安全特性——Content Security Policy (CSP)——就显得尤为关键。那么今天,让我们先从理论层面好好来梳理一下 CSP,看看它到底有哪些约束来保障我们的 web 内容的安全性,下一期我们再从代码演示上来分别展示。

CSP 本质上是建立一个内容安全白名单,通过 HTTP 头或 标签告知浏览器哪些外部资源(脚本、样式、图片、字体、框架等)允许被加载和执行。如果资源的来源不在白名单内,浏览器就会拒绝加载,从而有效地阻止了大部分 XSS 攻击以及其他类型的内容注入攻击。

如下图,抖音官网的响应头中关于 CSP 相关的配置

一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操

为什么需要 CSP?

想象一下,你的网站有一个留言板功能。如果没有对用户输入进行严格的过滤,攻击者可能会提交包含恶意 标签的留言。当其他用户浏览这条留言时,浏览器会解析并执行这个恶意脚本,造成 XSS 攻击。

传统的防御: 对用户输入进行 HTML 转义,例如将 < 转换为 <。但这依赖于开发者对所有潜在输入点的严格控制,容易遗漏。

CSP 的防御: 通过设置 CSP,你可以限制哪些域名下的脚本可以执行,甚至完全禁止页面执行内联脚本(...)或通过 eval() 执行字符串代码。这样一来,即使攻击者成功注入了 标签,如果它的来源或执行方式不符合 CSP 策略,浏览器也会拒绝执行它,从而大大降低了 XSS 的风险。

除了 XSS,CSP 还能防御:

  • 数据注入攻击: 通过限制表单提交的目标(form-action)。
  • 点击劫持: 通过限制页面是否可以在 中嵌套(frame-ancestors)。
  • 混淆内容攻击: 强制使用 HTTPS 连接(upgrade-insecure-requests)。

CSP 的工作原理

CSP 的核心是定义一系列指令 (Directives) 及其对应的允许来源 (Sources)。这些策略可以通过以下两种方式发送给浏览器:

  1. HTTP 响应头 (推荐): 在服务器的 HTTP 响应中添加 Content-Security-PolicyContent-Security-Policy-Report-Only 头部。

    Content-Security-Policy: default-src \'self\'; script-src \'self\' https://cdnjs.cloudflare.com; style-src \'self\' \'unsafe-inline\'

    这是最推荐的方式,因为它可以覆盖整个页面,并且策略是在页面内容解析之前生效的。

  2. 标签: 在页面的 中添加一个 标签。

    这种方式优先级较低,并且不能用于 report-urireport-toframe-ancestors 等指令。

Content-Security-Policy-Report-Only 头部:

这个头部用于报告违反 CSP 策略的行为,但并不阻止这些行为。这对于在生产环境中测试新的 CSP 策略非常有帮助。你可以在不影响用户体验的情况下,观察哪些资源会被新的策略阻止,然后根据报告调整策略,直到满意为止,最后再切换到 Content-Security-Policy 头部。

CSP 发展历程

时间 标准版本 状态 关键特性 网址 2012 CSP Level 1 已废弃 基本资源限制 https://www.w3.org/TR/CSP1/ 2014 CSP Level 2 推荐使用 nonce、hash、报告机制 https://www.w3.org/TR/CSP2/ 2021 CSP Level 3 草案 strict-dynamic、report-to https://www.w3.org/TR/CSP3/ 未来 CSP Level 4 规划中 新特性支持(待定)

浏览器兼容性

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-srcworker-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:...
  • \'sha256-\': 允许具有特定哈希值的内联脚本或样式。你可以计算内联脚本或样式的哈希值(支持 SHA256, SHA384, SHA512),并将其添加到 CSP 头中。

    • 示例 CSP 头:script-src \'sha256-abcdefg...\'
    • 示例 HTML:alert(\'Hello, CSP!\'); (你需要计算 alert(\'Hello, CSP!\'); 这段代码的 SHA256 哈希值)
  • \'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 也会被允许加载)

使用 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。一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操
    • 同源脚本 (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 违规的警告或错误信息。

一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操

  • 如果你的策略包含了 report-uri /report; 并且有违规发生,服务器端的控制台会打印出接收到的违规报告的 JSON 数据。

你可以修改 app.js 中的 cspPolicy 来实验不同的 CSP 策略,观察它们对页面行为的影响。例如,去掉 \'unsafe-inline\',看看内联脚本和样式是否被阻止。

构建工具 (Webpack/Vite) 的最佳实践

现代 Web 开发通常使用构建工具(如 Webpack 或 Vite)来打包、优化和处理资源。构建过程可能会产生一些 CSP 需要特别关注的问题:

  • 内联代码: 构建工具(尤其是 Webpack 的一些插件)可能会生成一些内联的运行时脚本或小的 CSS 块。这些内联代码如果没有被 CSP 允许,会导致应用无法正常运行。
  • 哈希文件名: 构建工具常常为了缓存优化,给输出文件添加哈希(例如 bundle.abcdef.js)。CSP 策略通常是基于来源路径,这与哈希文件名不冲突。

解决方案:

  1. 最小化内联代码: 尽量减少构建工具生成的内联脚本和样式。

  2. 使用 Nonce 或 Hash:

    • 实现: 构建完成后,计算所有内联脚本和样式的哈希值(SHA256, SHA384 或 SHA512)。将这些哈希值添加到 CSP 响应头的 script-src 和/或 style-src 指令中,例如 script-src \'sha256-HASH_OF_SCRIPT1\' \'sha256-HASH_OF_SCRIPT2\' \'self\' ...;
    • 这种方法对于每次构建都会变化的内联代码(如包含构建时间戳的代码)不太实用。
    • 实现: 服务器在每个请求时生成一个唯一的 base64 编码的 Nonce 值。将这个 Nonce 值添加到 CSP 响应头的 script-src 和/或 style-src 指令中,例如 script-src \'nonce-YOUR_NONCE_VALUE\' \'self\' ...;
    • 同时,将这个 Nonce 值传递给你的前端模板引擎,将它作为 nonce 属性添加到构建工具生成的内联 标签上。
    • 一些构建工具或其插件提供了 Nonce 集成的能力。例如,在使用 HTMLWebpackPlugin 生成 HTML 时,可以通过插件选项将 Nonce 注入到生成的标签中。
    • Nonce: 这是处理动态生成的内联脚本(如运行时代码、动态导入的启动脚本)的最佳方式。
    • Hash: 适用于内容不变的静态内联脚本或样式。
  3. 使用 \'strict-dynamic\' (与 Nonce/Hash 配合): 当使用 noncehash 允许了初始脚本后,可以使用 \'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\' 通常仍然有效作为回退。
  4. 配置允许的来源: 对于外部引用的资源(JS, CSS, Images 等),根据你的项目依赖,在 CSP 策略中明确列出所有允许的 CDN、API 服务等来源。

实施 CSP 的建议步骤

  1. 分析现有应用: 了解你的应用加载了哪些外部资源,哪些使用了内联脚本或样式,哪些使用了 eval()
  2. report-only 开始: 在生产环境中使用 Content-Security-Policy-Report-Only 头部和 report-uri 指令。部署策略,并收集违规报告,分析哪些合法的资源被阻止了。
  3. 逐步收紧策略: 根据报告调整策略,逐渐移除 \'unsafe-inline\'\'unsafe-eval\',明确列出允许的来源。优先解决 script-srcdefault-src 的问题。
  4. 考虑 Nonce 或 Hash: 如果确实需要内联脚本(如构建工具生成的运行时代码),集成 Nonce 或 Hash 机制。考虑使用 \'strict-dynamic\' 简化管理。
  5. 切换到强制模式: 当你确信策略不会阻止应用的正常功能时,将 Content-Security-Policy-Report-Only 切换为 Content-Security-Policy
  6. 持续监控: 即使在强制模式下,也要继续使用 report-urireport-to 收集报告,以便及时发现新的问题或潜在的攻击尝试。
  7. 结合其他安全措施: 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资料,毕竟实战是检验真理的唯一标准嘛~

一文吃透 CSP:现代 Web 安全的核心机制_csp策略实操

四、网络安全面试题

最后,我们所有的作为都是为就业服务的,所以关键的临门一脚就是咱们的面试题内容,所以面试题板块是咱们不可或缺的部分,这里我给大家准备的就是我在面试期间准备的资料。

在这里插入图片描述

网安其实不难,难的是坚持和相信自己,我的经验是既然已经选定网安你就要相信它,相信它能成为你日后进阶的高效渠道,这样自己才会更有信念去学习,才能在碰到困难的时候坚持下去。

机会属于有准备的人,这是一个实力的时代。人和人之间的差距不在于智商,而在于如何利用业余时间,只要你想学习,什么时候开始都不晚,不要担心这担心那,你只需努力,剩下的交给时间!

这份完整版的网络安全学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

img