- Published on
CSP(内容安全策略)
在现代 Web 安全的纵深防御体系中,内容安全策略 (Content Security Policy, CSP) 是一道至关重要的防线。它是一种由 W3C 标准化的计算机安全机制,其核心目标是检测并缓解特定类型的攻击,尤其是跨站脚本攻击 (XSS) 和数据注入攻击。CSP 赋予了 Web 应用开发者一套精细的控制权,允许其明确地定义一个“白名单”,告诉浏览器只允许加载和执行来自可信来源的资源。
CSP 的部署与工作原理
CSP 通过服务器发送一个 Content-Security-Policy HTTP 响应头来部署。浏览器接收到此头部后,会严格遵循其中定义的策略来加载和执行页面资源。
- HTTP 响应头 (推荐):
Content-Security-Policy: <policy-string>,这是最常用、最安全的部署方式。 <meta>标签:<meta http-equiv="Content-Security-Policy" content="<policy-string>">,可以作为备用,但功能受限(例如,无法使用frame-ancestors、report-uri等指令)。
其核心机制是基于“指令 (directives)”的白名单策略。开发者通过组合不同的指令,来为不同类型的资源(脚本、样式、图片等)设定允许加载的来源列表。
核心指令 (Directives) 详解
CSP 策略由一个或多个指令组成,指令之间用分号 ; 分隔。
Content-Security-Policy: default-src 'self'; script-src 'self' https://apis.google.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';
default-src 'self': 默认只信任同源资源。'self'是一个特殊的关键字。script-src 'self' https://apis.google.com: 脚本只能从同源或https://apis.google.com加载。style-src 'self' 'unsafe-inline': 样式表只能从同源加载,但允许内联样式(<style>标签和style属性),这在某些情况下是必要的,但会降低安全性。img-src 'self' data:: 图片只能从同源或以data:URL 的形式加载。frame-ancestors 'none': 禁止页面被嵌入到任何iframe中。
常用资源指令
default-src: 这是一个备用指令,用于为大多数其他资源指令(如script-src,style-src等)提供默认值。script-src: 定义允许加载和执行 JavaScript 的有效来源。style-src: 定义允许加载和应用样式的有效来源(CSS 文件、<style>标签等)。img-src: 定义允许加载图片的有效来源。font-src: 定义允许加载字体的有效来源。connect-src: 限制通过脚本接口(如fetch,XMLHttpRequest,WebSocket)可以连接的 URL。media-src: 定义允许加载 HTML5<audio>和<video>元素的有效来源。object-src: 定义允许加载<object>,<embed>和<applet>标签中插件的有效来源。出于安全考虑,通常建议设置为'none'。child-src: 定义允许通过子上下文(如<iframe>,<worker>等)加载的资源来源。frame-src: CSP Level 3 引入,用于更细粒度地控制<frame>和<iframe>的来源。如果同时指定child-src和frame-src,那么<iframe>遵循frame-src,其他子上下文遵循child-src。
文档指令
frame-ancestors: 指定哪些源可以通过<frame>,<iframe>,<object>等标签嵌入当前页面。这是防御点击劫持 (Clickjacking) 的最有效手段。frame-ancestors 'self': 只允许被同源页面嵌入。frame-ancestors 'none': 完全禁止页面被任何来源嵌入。
报告指令
report-uri/report-to: 指定当 CSP 策略被违反时,浏览器将违规报告(一个 JSON 对象)发送到的 URL。report-uri是一个较早的指令,而report-to是基于新的 Reporting API 的现代化指令。
应对内联脚本与样式
默认情况下,一个严格的 CSP 会禁止所有内联脚本和样式(例如 <script>...</script> 和 onclick="..."),因为这是 XSS 攻击最常见的向量。使用 'unsafe-inline' 关键字可以重新启用它们,但这会削弱 CSP 的安全屏障。现代 CSP 提供了两种更安全的技术来允许特定的内联脚本。
使用 nonce (Number used once)
服务器为每一次页面请求生成一个唯一的、不可预测的随机令牌(通常是 Base64 编码的字符串)。然后,将这个令牌同时放入 CSP 头的 script-src 或 style-src 指令中,并作为 nonce 属性添加到 HTML 的 <script> 或 <style> 标签上。浏览器只会执行 nonce 属性值与 CSP 头中声明的令牌相匹配的内联脚本。
服务器响应头:
Content-Security-Policy: script-src 'nonce-aBcDeFg12345'
HTML 页面:
<script nonce="aBcDeFg12345">console.log("This script will execute.");</script>
<script nonce="aBcDeFg1234567">alert("This script will be blocked by CSP.");</script>
这个 nonce 值必须在每次页面加载时都重新生成,不可复用。
使用 hash (哈希值)
对于静态的、内容不变的内联脚本,可以预先计算出其内容的 SHA256、SHA384 或 SHA512 哈希值。然后,将这个哈希值(Base64 编码后)放入 CSP 头的 script-src 或 style-src 指令中。浏览器在解析页面时,会计算它遇到的每一个内联脚本的哈希值,并与头部中提供的哈希白名单进行比对。
- Nonce: 适用于动态生成的内联脚本,因为服务器可以在生成脚本的同时生成 nonce。
- Hash: 适用于静态的、在构建时内容就已确定的内联脚本。
部署策略
直接部署一个严格的 CSP 可能会破坏现有应用的功能。因此,推荐采用循序渐进的策略:
- 使用
Content-Security-Policy-Report-Only头: 在此模式下,浏览器仅报告违规行为,而不实际阻止任何资源的加载。这允许你在不影响用户的情况下,收集违规报告并逐步完善你的策略。 - 分析报告: 通过
report-uri或report-to收集违规报告,识别出所有必须被加入白名单的资源来源。 - 切换为强制模式: 当违规报告不再出现时,将
Content-Security-Policy-Report-Only头替换为Content-Security-Policy头,正式启用强制执行策略。