内容安全政策

Mike West
Joe Medley
Joe Medley

Web 的安全模型植根于 同源政策。代码 来自 https://mybank.com 的网站应只能访问 https://mybank.com 的 数据,也绝不应允许 https://evil.example.com 访问。 每个源都与网络的其余部分隔离开来,这为开发者提供了一个安全的 构建和畅玩游戏从理论上来说,这非常棒。在 攻击者已找到聪明的方式来破坏系统。

跨站脚本攻击 (XSS) 例如,通过诱使网站进入同源攻击来绕过同源政策, 分发恶意代码和预期内容。这是一个 这是因为浏览器将页面上显示的所有代码 是该网页安全源的合法部分。通过 XSS 备忘单 是攻击者可能使用的一种古老但具有代表性的方法 来破坏这种信任。如果攻击者成功 注入任何代码,几乎游戏结束了:用户会话数据 而本应保密的信息也会泄露给坏人 伙计们,显然,如果可能的话,我们很愿意阻止这种情况。

本概览重点介绍一个可显著降低风险和 XSS 攻击在现代浏览器中的影响:内容安全政策 (CSP)。

要点

  • 使用许可名单来告知客户端允许和禁止的内容。
  • 了解可以使用哪些指令。
  • 了解它们使用的关键字。
  • 内嵌代码和 eval() 被视为有害。
  • 先向服务器举报违反政策的行为,然后再强制执行。

来源许可名单

被 XSS 攻击利用的问题是浏览器无法区分 您应用的一部分脚本和 由第三方恶意注入例如, 从这个网页的底部加载并执行代码 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),以及从当前页面的来源开始。

控制台错误:已拒绝加载脚本“http://evil.example.com/evil.js”因为它违反了以下内容安全政策指令:script-src 'self'https://apis.google.com

定义此政策后,浏览器将直接抛出错误, 从任何其他来源加载脚本。当聪明的攻击者设法 将代码注入您的网站,它们就会直接显示错误消息, 比他们预期的成功更胜一筹

政策适用于各种资源

虽然脚本资源是最明显的安全风险,但 CSP 可提供丰富的 一组政策指令,用于对资源进行相当精细的控制 允许某个页面加载。您已经了解了 script-src,了解这个概念 都应该清晰明确

我们快速浏览一下其余资源指令。下方列表 表示自级别 2 时指令的状态。级别 3 规范已发布,但在主要版本中基本未实现

  • base-uri 会限制可在页面的 <base> 元素中显示的网址。
  • child-src 列出了 worker 和嵌入式框架内容的网址。对于 例如:child-src https://youtube.com 用于从以下位置嵌入视频: YouTube,但并非来自其他来源。
  • connect-src 用于限制您可以连接的源(通过 XHR、 WebSockets 和 EventSource)。
  • font-src 用于指定可以提供网页字体的来源。Google 网络 可通过 font-src https://themes.googleusercontent.com 启用字体。
  • 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 用于指定浏览器在遇到 内容安全政策。此指令不能用于 <meta> 代码。
  • style-srcscript-src 的对应样式表。
  • upgrade-insecure-requests 会指示用户代理重写网址架构, 将 HTTP 更改为 HTTPS此指令适用于拥有大量 需要重写的旧网址。
  • worker-src 是 CSP 3 级指令,可限制 作为工作器、共享工作器或 Service Worker 进行加载。自 2017 年 7 月起 指令包含 植入方式有限

默认情况下,指令的适用范围很广。如果您没有为 假设为 font-src,则该指令默认的行为如 但您已指定 * 作为有效来源(例如,您可以从 任何地点,没有任何限制)。

您可以通过指定 default-src 来替换此默认行为。 指令。此指令定义了大多数应用的默认值, 您未指定的指令通常,这适用于 以 -src 结尾。如果 default-src 设置为 https://example.com,并且失败 指定 font-src 指令,然后您可以从 https://example.com,不能投放到其他任何地方我们只指定了 script-src 也就是说,可以从 任何来源。

以下指令不使用 default-src 作为后备指令。请注意 如果不进行设置,就等同于允许加载任何内容。

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

您可以根据自己的实际需要 使用任意数量的指令 只需在 HTTP 标头中列出每个类别 带分号的指令。请务必列出所有 单个指令中需要特定类型的资源。如果您撰写的是 比如 script-src https://host1.com; script-src https://host2.com 那么系统会忽略第二个指令。类似如下的内容: 将两个来源正确指定为有效来源:

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

例如,如果您有一个应用程序从 内容分发网络(例如 https://cdn.example.net),并且知道 不需要任何加框内容或插件,那么您的政策可能看起来 如下所示:

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

实现细节

您会看到 X-WebKit-CSPX-Content-Security-Policy 标头以各种不同格式显示。 其他在线教程。今后,请忽略这些带有前缀的 标头。现代浏览器(IE 除外)支持无前缀 Content-Security-Policy 标头。您应该使用此类标头。

无论您使用哪种标题,政策都是按网页定义的: 您需要将 HTTP 标头连同您要发送的每个响应一起 以确保受到保护这提供了极大的灵活性 政策。假设有一组 您网站上的网页包含 +1 按钮,而其他网页则没有:您可以允许 按钮代码仅在必要时加载。

每条指令中的源列表都是灵活的。您可以指定来源 架构(data:https:),或仅主机名指定范围 (example.com,匹配该主机上的任何源站:任何架构、任何端口) 完全限定 URI(仅与 HTTPS 匹配,https://example.com:443example.com 且仅限端口 443)。可以使用通配符,但只能以 scheme 的形式表示, 如果 *://*.example.com:* 位于某个端口或主机名最左边的位置, 匹配 example.com 的所有子网域(但不是 example.com 本身),使用 在任何端口上进行传输

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

  • 如您所料,'none' 不会匹配任何内容。
  • 'self' 与当前来源匹配,但不匹配其子网域。
  • 'unsafe-inline' 允许使用内嵌 JavaScript 和 CSS。(我们会在 详细介绍。)
  • 'unsafe-eval' 允许使用 eval 等文本到 JavaScript 机制。(我们得到 到此为止。)

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

沙盒

还有一条指令值得探讨:sandbox。有点用 它限制了那些 网页可占用的资源,而不是网页可加载的资源。如果 若有 sandbox 指令,则网页会被视为已加载 使用 sandbox 属性在 <iframe> 内实现。这可能有 对网页的影响:将网页强制设为唯一的来源,并阻止表单生成 提交内容等等。这有点不在本文的讨论范围之内, 您可以在 “沙盒”部分

元标记

CSP 首选的传送机制是 HTTP 标头。不过,它可能会很有用 直接在标记中设置网页上的政策。为此,请使用 <meta> 标记: http-equiv 属性:

<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>sendMyDataToEvilDotCom();</script>)、 浏览器没有办法将其与合法的 内嵌脚本标记。CSP 可通过完全禁止内联脚本来解决此问题: 只有这样才能确定

此禁令不仅包括直接嵌入 script 标记的脚本,也包括 内嵌事件处理脚本和 javascript: 网址,您需要将 将 script 标记转换为外部文件,并将 javascript: 网址和 <a ... onclick="[JAVASCRIPT]"> 替换为相应的 addEventListener() 调用。例如: 您可以改写以下内容:

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

更改为类似如下的内容:

<!-- amazing.html -->
<script src="amazing.js"></script>
<button id="amazing">Am I amazing?</button>

<div style="clear:both;"></div>
// amazing.js
function doAmazingThings() {
  alert('YOU AM AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
  document.getElementById('amazing').addEventListener('click', doAmazingThings);
});

除了与 Google Cloud 的 CSP;这已经是最佳做法了,无论您是否使用 CSP。内嵌 JavaScript 混合结构和行为的方式正是您不应采用的方式。 外部资源更易于浏览器缓存, 并有助于编译和缩减大小。你会写得更好 将代码移入外部资源。

内嵌样式的处理方式相同:style 属性和 style 代码应合并为外部样式表,以防止出现 各种出人意料的巧妙 CSS 启用的数据渗漏方法。

如果必须具有内嵌脚本和样式,则可将其启用 在 script-srcstyle-src 中将 'unsafe-inline' 添加为允许的来源 指令。您也可以使用 Nonce 或哈希值(见下文),但您真的不应这样做。 禁止内嵌脚本是 CSP 提供的最大安全性优势, 禁用内联样式同样可以提高应用程序的安全性。有点像 以确保在迁移所有代码后一切正常运行 出格,但也值得您做出取舍。

如果您一定要使用

CSP 级别 2 提供对内联脚本的向后兼容性,即允许您执行以下操作: 使用加密 Nonce (number) 将特定的内嵌脚本添加到许可名单中, 使用一次)或哈希值。虽然这可能很麻烦, 处理各种事务

要使用 Nonce,请为您的脚本标记提供一个 Nonce 属性。其值必须与 1 匹配 显示在“可信来源”列表中例如:

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

现在,将 Nonce 添加到附加到 nonce- 关键字的 script-src 指令中。

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

请注意,您必须为每个网页请求重新生成 Nonce,且它们必须是 不可猜测。

哈希值的工作方式与此大致相同。您无需将代码添加到脚本代码 创建脚本本身的 SHA 哈希,并将其添加到 script-src 指令中。 例如,假设您的网页包含以下内容:

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

您的政策将包含以下内容:

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

这里有几点需要注意。sha*- 前缀指定算法 生成哈希值。上面的示例中使用了 sha256-。CSP 同样 支持 sha384-sha512-。生成哈希值时,请不要包含 <script> 标记。此外,大小写和空格也很重要,包括开头或 尾随空格。

在 Google 上搜索如何生成 SHA 哈希,可引导您找到 语言数量。使用 Chrome 40 或更高版本,您可以打开开发者工具,然后 重新加载页面。“控制台”标签将包含错误消息以及正确的 sha256 哈希。

评估也是

即使攻击者无法直接注入脚本,也有可能利用 将原本的休眠文本转换为可执行的 JavaScript 并代为执行eval()(新) Function()、setTimeout([string], ...)setInterval([string], ...) 都是注入的向量 最终可能会执行意料之外的恶意内容。CSP 的默认值 是完全阻止所有这些向量。

这对您构建应用的方式有不少影响:

  • 您必须通过内置的 JSON.parse 解析 JSON,而不是依赖于 eval。原生 JSON 操作可在 自 IE8 起的所有浏览器 非常安全
  • 重写当前正在进行的任何 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' 作为允许的来源 指令,但我们强烈建议您不要这样做。禁止 字符串使得攻击者在未经授权的情况下更难执行 植入代码

报告

CSP 在客户端屏蔽不可信资源的能力, 但如果能收到某种通知 以便您可以找出并消除所有 首先是恶意注入为此,您可以指示 浏览器到POST某个营业地点的 JSON 格式的违规报告 在 report-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),那么该网页的引荐来源网址(请注意,与 HTTP 请求不同 标头字段、键没有拼写错误)、违反 网页政策(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 1 在 Chrome、Safari 和 Firefox 中非常实用,但 支持 IE 10。您可以 访问 caniuse.com 查看具体信息。CSP 级别 2 已在 Chrome 中推出 版本 40。Twitter 和 Facebook 等大规模网站部署了该标头 (Twitter 的案例研究非常值得一读),而且该标准已经 供您开始在自己网站上部署。

为您的应用制定政策的第一步是,评估 加载的资源一旦您了解了 在应用中整合了各种信息,请根据这些信息设置政策 要求。我们来看一些常见的使用场景,并确定 能够在 CSP 的保护范围内为它们提供支持。

用例 1:社交媒体微件

  • Google 的 +1 按钮 包含来自 的脚本https://apis.google.com,并嵌入了来自 的<iframe> https://plusone.google.com。您需要制定同时包含这两项的政策 源以嵌入该按钮。最低政策为 script-src https://apis.google.com; child-src https://plusone.google.com。您还需要 以确保将 Google 提供的 JavaScript 代码段提取到 外部 JavaScript 文件。如果您有使用 frame-src 的基于 1 级的政策 级别 2 要求您将其更改为 child-src。不再需要这样做了 。

  • Facebook 的 Like 按钮 有许多实现选项我们建议坚持使用 <iframe> 版本,因为它已安全地与网站的其余部分隔离开来。它 需要 child-src https://facebook.com 指令才能正常运行。注意事项 默认情况下,Facebook 提供的 <iframe> 代码会加载一个相对地址, 网址://facebook.com。对其进行更改,以明确指定 HTTPS: https://facebook.com。除非非必要,否则没有理由使用 HTTP。

  • Twitter 的 Tweet 按钮 都依赖于对脚本和框架的访问权限, https://platform.twitter.com。(Twitter 同样通过 default;请在本地复制/粘贴时修改代码以指定 HTTPS)。 只要您移动 JavaScript 代码段,script-src https://platform.twitter.com; child-src https://platform.twitter.com 就大功告成了 添加到外部 JavaScript 文件中。

  • 其他平台有类似的要求,可以通过类似方式解决。 我们建议将 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

用例 2:锁定

我们假设您经营一家银行网站,并希望确保 只能加载您自己编写的资源。在这种情况下 请先设置一个会屏蔽所有内容的默认政策 (default-src 'none'),然后在此基础上进一步构建。

假设银行从 CDN 加载所有图片、样式和脚本,网址为 https://cdn.mybank.net,然后通过 XHR 连接到 https://api.mybank.com/ 提取各种数据会使用框架,但仅限于 网站(无第三方源)。网站上没有 Flash,没有字体,也没有 extra。我们可以发送的最严格的 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'

用例 3:仅 SSL

婚戒论坛管理员想要确保所有资源都 仅通过安全通道加载,但不会编写很多代码;重写 大量的第三方论坛软件, 内嵌脚本和风格超出了他的能力范围。以下政策为 生效日期:

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

即使 default-src 中指定了 https:,脚本和样式也会 指令不会自动继承该来源。每条指令 覆盖该特定资源类型的默认值。

未来展望

内容安全政策级别 2 是 候选建议。W3C 的 Web 应用程序安全工作组 已开始处理规范的下一次迭代, 内容安全政策级别 3

如果您对这些即将推出的功能感兴趣, 浏览 public-webappsec@ 邮寄名单归档, 或者您也可以自己加入。

反馈