内容安全策略

内容安全策略可以显著降低现代浏览器中跨站脚本攻击的风险和影响。

浏览器支持

  • Chrome: 25.
  • Edge: 14.
  • Firefox: 23.
  • Safari: 7.

来源

Web 的安全模型基于同源政策。例如,来自 https://mybank.com 的代码必须只能访问 https://mybank.com 的数据,而绝不允许 https://evil.example.com 访问。理论上,每个来源都与 Web 的其余部分隔离,为开发者提供了一个安全的沙盒来构建内容。然而,在实践中,攻击者已经找到了几种破坏系统的方法。

例如,跨站脚本 (XSS) 攻击通过诱骗网站在提供预期内容的同时提供恶意代码,从而绕过同源政策。这是一个巨大的问题,因为浏览器信任页面上显示的所有代码都是该页面安全来源的合法组成部分。XSS 备忘单是一个旧的但具有代表性的横截面,展示了攻击者可能使用的方法,通过注入恶意代码来破坏这种信任。如果攻击者成功注入任何代码,他们就会危及用户会话并获得对私密信息的访问权限。

本页概述了内容安全策略 (CSP) 作为一种策略,用于降低现代浏览器中 XSS 攻击的风险和影响。

CSP 的组成部分

要实施有效的 CSP,请采取以下步骤

  • 使用允许列表告知客户端允许什么和不允许什么。
  • 了解有哪些指令可用。
  • 了解它们采用的关键字。
  • 限制内联代码和 eval() 的使用。
  • 在强制执行策略之前,向您的服务器报告策略违规行为。

来源允许列表

XSS 攻击利用了浏览器无法区分应用程序的脚本和第三方恶意注入的脚本。例如,此页面底部的 Google +1 按钮会加载并执行来自 https://apis.google.com/js/plusone.js 的代码,其上下文为此页面的来源。我们信任该代码,但我们不能指望浏览器自行判断来自 apis.google.com 的代码可以安全运行,而来自 apis.evil.example.com 的代码可能不安全。浏览器会愉快地下载并执行页面请求的任何代码,而不管来源如何。

CSP 的 Content-Security-Policy HTTP 标头允许您创建受信任内容来源的允许列表,并告知浏览器仅执行或呈现来自这些来源的资源。即使攻击者可以找到漏洞来注入脚本,该脚本也不会与允许列表匹配,因此不会被执行。

我们信任 apis.google.com 会提供有效的代码,并且我们相信自己也能做到。以下是一个示例策略,该策略仅允许脚本在来自这两个来源之一时执行

Content-Security-Policy: script-src 'self' https://apis.google.com

script-src 是一项指令,用于控制页面的脚本相关权限集。此标头将 'self' 作为脚本的有效来源之一,并将 https://apis.google.com 作为另一个有效来源。浏览器现在可以从 apis.google.com 通过 HTTPS 以及从当前页面的来源下载和执行 JavaScript,但不能从任何其他来源下载和执行。如果攻击者将代码注入您的网站,浏览器会抛出错误并且不运行注入的脚本。

Console error: Refused to load the script 'http://evil.example.com/evil.js' because it violates the following Content Security Policy directive: script-src 'self' https://apis.google.com
当脚本尝试从不在允许列表中的来源运行时,控制台会显示错误。

策略适用于各种资源

CSP 提供了一组策略指令,可以对页面允许加载的资源进行精细控制,包括上一个示例中的 script-src

以下列表概述了级别 2 中的其余资源指令。级别 3 规范 已起草,但它在主要浏览器中在很大程度上尚未实施

base-uri
限制可以出现在页面的 <base> 元素中的 URL。
child-src
列出 worker 和嵌入式框架内容的 URL。例如,child-src https://youtube.com 允许嵌入来自 YouTube 的视频,但不允许来自其他来源的视频。
connect-src
限制可以使用 XHR、WebSocket 和 EventSource 连接到的来源。
font-src
指定可以提供 Web 字体的来源。例如,您可以使用 font-src https://themes.googleusercontent.com 允许 Google 的 Web 字体。
form-action
列出从 <form> 标记提交的有效端点。
frame-ancestors
指定可以嵌入当前页面的来源。此指令适用于 <frame><iframe><embed><applet> 标记。它不能在 <meta> 标记或 HTML 资源中使用。
frame-src
此指令在级别 2 中已弃用,但在级别 3 中已恢复。如果它不存在,浏览器将回退到 child-src
img-src
定义可以从中加载图像的来源。
media-src
限制允许提供视频和音频的来源。
object-src
允许控制 Flash 和其他插件。
plugin-types
限制页面可以调用的插件类型。
report-uri
指定浏览器在内容安全策略被违反时向其发送报告的 URL。此指令不能在 <meta> 标记中使用。
style-src
限制页面可以使用样式表的来源。
upgrade-insecure-requests
指示用户代理通过将 HTTP 更改为 HTTPS 来重写 URL 方案。此指令适用于具有大量旧 URL 需要重写的网站。
worker-src
CSP 级别 3 指令,限制可以作为 worker、共享 worker 或服务 worker 加载的 URL。截至 2017 年 7 月,此指令的实施有限

默认情况下,浏览器从任何来源加载关联的资源,没有限制,除非您使用特定指令设置策略。要覆盖默认值,请指定 default-src 指令。此指令定义任何以 -src 结尾的未指定指令的默认值。例如,如果您将 default-src 设置为 https://example.com,并且您未指定 font-src 指令,则您只能从 https://example.com 加载字体。

以下指令不使用 default-src 作为回退。请记住,未能设置它们与允许任何内容相同

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

基本 CSP 语法

要使用 CSP 指令,请在 HTTP 标头中列出它们,指令之间用冒号分隔。请确保在单个指令中列出特定类型的所有必需资源,如下所示

script-src https://host1.com https://host2.com

以下是多个指令的示例,在本例中,对于一个 Web 应用程序,它从内容分发网络 https://cdn.example.net 加载其所有资源,并且不使用框架内容或插件

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

实施细节

现代浏览器支持无前缀的 Content-Security-Policy 标头。这是推荐的标头。您可能在在线教程中看到的 X-WebKit-CSPX-Content-Security-Policy 标头已弃用。

CSP 是在逐页基础上定义的。您需要为要保护的每个响应发送 HTTP 标头。这使您可以根据特定页面的特定需求微调策略。例如,如果您的网站中的一组页面具有 +1 按钮,而其他页面没有,则您可以仅在必要时允许加载按钮代码。

每个指令的来源列表都很灵活。您可以按方案 (data:, https:) 指定来源,或者范围从仅主机名 (example.com,它匹配该主机上的任何来源:任何方案,任何端口) 到完全限定的 URI (https://example.com:443,它仅匹配 HTTPS,仅 example.com,并且仅匹配端口 443)。接受通配符,但仅作为方案、端口或主机名的最左侧位置:*://*.example.com:* 将匹配 example.com 的所有子域(但匹配 example.com 本身),使用任何方案,在任何端口上。

来源列表还接受四个关键字

  • 'none' 不匹配任何内容。
  • 'self' 匹配当前来源,但不匹配其子域。
  • 'unsafe-inline' 允许内联 JavaScript 和 CSS。有关更多信息,请参阅 避免内联代码
  • 'unsafe-eval' 允许文本到 JavaScript 的机制,如 eval。有关更多信息,请参阅 避免 eval()

这些关键字需要单引号。例如,script-src 'self'(带引号)授权从当前主机执行 JavaScript;script-src self(不带引号)允许来自名为“self”的服务器的 JavaScript(而不是来自当前主机),这可能不是您的本意。

沙盒化您的页面

还有一个值得讨论的指令:sandbox。它与我们查看的其他指令有点不同,因为它限制了页面可以执行的操作,而不是页面可以加载的资源。如果存在 sandbox 指令,则该页面将被视为好像它加载在带有 sandbox 属性的 <iframe> 内。这会对页面产生广泛的影响:强制页面进入唯一来源,并阻止表单提交等。这有点超出本页的范围,但您可以在 HTML5 规范的“沙盒化”部分中找到有关有效沙盒属性的完整详细信息。

meta 标签

CSP 的首选交付机制是 HTTP 标头。但是,直接在标记中设置页面策略可能很有用。使用带有 http-equiv 属性的 <meta> 标记来执行此操作

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">

这不能用于 frame-ancestorsreport-urisandbox

避免内联代码

尽管 CSP 指令中使用的基于来源的允许列表功能强大,但它们无法解决 XSS 攻击造成的最大威胁:内联脚本注入。如果攻击者可以注入直接包含某些恶意负载的 script 标记(例如 <script>sendMyDataToEvilDotCom()</script>),则浏览器无法将其与合法的内联 script 标记区分开来。CSP 通过完全禁止内联 script 来解决此问题。

此禁令不仅包括直接嵌入在 script 标记中的脚本,还包括内联事件处理程序和 javascript: URL。您需要将 script 标记的内容移动到外部文件,并将 javascript: URL 和 <a ... onclick="[JAVASCRIPT]"> 替换为适当的 addEventListener() 调用。例如,您可以将以下内容从

<script>
    function doAmazingThings() {
    alert('YOU ARE AMAZING!');
    }
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>

重写为更像这样的内容

<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
    alert('YOU ARE AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('amazing')
    .addEventListener('click', doAmazingThings);
});

重写的代码不仅与 CSP 兼容,而且还与 Web 设计最佳实践保持一致。内联 JavaScript 以使代码混乱的方式混合了结构和行为。缓存和编译也更复杂。将您的代码移动到外部资源可以使您的页面性能更好。

强烈建议将内联 style 标记和属性移动到外部样式表,以保护您的网站免受基于 CSS 的 数据泄露攻击

如何临时允许内联脚本和样式

您可以通过在 script-srcstyle-src 指令中添加 'unsafe-inline' 作为允许的来源来启用内联脚本和样式。CSP 级别 2 还允许您使用加密 nonce(一次性使用的数字)或哈希值将特定的内联脚本添加到您的允许列表中,如下所示。

要使用 nonce,请为您的 script 标记提供一个 nonce 属性。其值必须与受信任来源列表中的值匹配。例如

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
    // Some inline code I can't remove yet, but need to as soon as possible.
</script>

按照 nonce- 关键字将 nonce 添加到您的 script-src 指令

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

Nonce 必须为每个页面请求重新生成,并且它们必须是无法猜测的。

哈希值的工作方式类似。无需向 script 标记添加代码,而是创建 script 本身的 SHA 哈希值,并将其添加到 script-src 指令。例如,如果您的页面包含此内容

<script>alert('Hello, world.');</script>

您的策略必须包含此内容

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

sha*- 前缀指定生成哈希值的算法。上一个示例使用 sha256-,但 CSP 也支持 sha384-sha512-。生成哈希值时,请省略 <script> 标记。大小写和空格很重要,包括前导和尾随空格。

任何数量的语言都提供了生成 SHA 哈希值的解决方案。使用 Chrome 40 或更高版本,您可以打开 DevTools,然后重新加载您的页面。“控制台”选项卡显示错误消息,其中包含每个内联脚本的正确 SHA-256 哈希值。

避免 eval()

即使攻击者无法直接注入脚本,他们也可能能够诱骗您的应用程序将输入文本转换为可执行的 JavaScript,并代表他们执行它。eval()new Function()setTimeout([string], …)setInterval([string], ...) 都是攻击者可以用来通过注入的文本执行恶意代码的向量。CSP 对此风险的默认响应是完全阻止所有这些向量。

这对您构建应用程序的方式有以下影响

  • 您必须使用内置的 JSON.parse 解析 JSON,而不是依赖 eval自 IE8 以来的每个浏览器中都提供了安全的 JSON 操作。
  • 您必须使用内联函数而不是字符串来重写您进行的任何 setTimeoutsetInterval 调用。例如,如果您的页面包含以下内容

    setTimeout("document.querySelector('a').style.display = 'none';", 10);
    

    将其重写为

    setTimeout(function () {
        document.querySelector('a').style.display = 'none';
    }, 10);
      ```
    
  • 避免在运行时进行内联模板化。许多模板库经常使用 new Function() 来加速运行时模板生成,这允许评估恶意文本。某些框架开箱即用地支持 CSP,在缺少 eval 的情况下回退到强大的解析器。AngularJS 的 ng-csp 指令就是一个很好的例子。但是,我们建议改用提供预编译的模板语言,例如 Handlebars。预编译您的模板可以使用户体验比最快的运行时实现更快,并使您的网站更安全。

如果 eval() 或其他文本到 JavaScript 的函数对您的应用程序至关重要,您可以通过在 script-src 指令中添加 'unsafe-eval' 作为允许的来源来启用它们。我们强烈建议不要这样做,因为它会带来代码注入风险。

报告策略违规行为

为了将可能允许恶意注入的错误通知服务器,您可以告知浏览器将 JSON 格式的违规报告 POSTreport-uri 指令中指定的位置

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

这些报告如下所示

{
    "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
    }
}

该报告包含用于查找策略违规原因的有用信息,包括发生违规的页面 (document-uri)、该页面的 referrer、违反页面策略的资源 (blocked-uri)、违反的具体指令 (violated-directive) 以及页面的完整策略 (original-policy)。

仅报告

如果您刚开始使用 CSP,我们建议使用仅报告模式来评估您的应用程序的状态,然后再更改您的策略。为此,不要发送 Content-Security-Policy 标头,而是发送 Content-Security-Policy-Report-Only 标头

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

在仅报告模式下指定的策略不会阻止受限资源,但它会将违规报告发送到您指定的位置。您甚至可以同时发送两个标头,以强制执行一个策略,同时监视另一个策略。这是在强制执行当前策略的同时测试 CSP 更改的好方法:为新策略启用报告,监视违规报告并修复任何错误,并且当您对新策略感到满意时,开始强制执行它。

真实世界的使用

为您的应用程序制定策略的第一步是评估其加载的资源。当您了解应用程序的结构后,请根据其要求创建策略。以下部分将介绍一些常见的用例以及遵循 CSP 指南来支持这些用例的决策过程。

社交媒体小部件

  • Facebook 的 “赞”按钮 有多种实施选项。我们建议使用 <iframe> 版本,以使其与您网站的其余部分沙盒隔离。它需要 child-src https://facebook.com 指令才能正常运行。
  • X 的 “推文”按钮 依赖于对脚本的访问。将其提供的脚本移动到外部 JavaScript 文件中,并使用指令 script-src https://platform.twitter.com; child-src https://platform.twitter.com
  • 其他平台具有类似的要求,并且可以类似地解决。要测试这些资源,我们建议将 default-src 设置为 'none' 并查看您的控制台以确定您需要启用哪些资源。

要使用多个小部件,请按如下方式组合指令

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

锁定

对于某些网站,您需要确保只能加载本地资源。以下示例为银行网站开发 CSP,从阻止所有内容 (default-src 'none') 的默认策略开始。

该网站从 CDN https://cdn.mybank.net 加载所有图像、样式和脚本,并使用 XHR 连接到 https://api.mybank.com/ 以检索数据。它使用框架,但仅用于网站本地的页面(没有第三方来源)。网站上没有 Flash,没有字体,没有其他内容。它可以发送的最严格的 CSP 标头是这个

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

仅限 SSL

以下是论坛管理员的示例 CSP,他们希望确保其论坛上的所有资源都仅使用安全通道加载,但他们不熟悉编码,也没有资源来重写充满内联脚本和样式的第三方论坛软件

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

尽管在 default-src 中指定了 https:,但 script 和 style 指令不会自动继承该来源。每个指令都会覆盖该特定资源类型的默认值。

CSP 标准开发

内容安全策略级别 2 是 W3C 推荐标准。W3C 的 Web 应用程序安全工作组正在开发该规范的下一个迭代版本,即 内容安全策略级别 3

要关注有关这些即将推出的功能的讨论,请参阅 public-webappsec@ 邮件列表存档