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](https://developer.chrome.google.cn/static/docs/privacy-security/csp/image/console-error-refused-l-524026adbb83a.png?authuser=9&hl=da)
定义此政策后,浏览器将直接抛出错误, 从任何其他来源加载脚本。当聪明的攻击者设法 将代码注入您的网站,它们就会直接显示错误消息, 比他们预期的成功更胜一筹
政策适用于各种资源
虽然脚本资源是最明显的安全风险,但 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-src
是script-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-CSP
和 X-Content-Security-Policy
标头以各种不同格式显示。
其他在线教程。今后,请忽略这些带有前缀的
标头。现代浏览器(IE 除外)支持无前缀
Content-Security-Policy
标头。您应该使用此类标头。
无论您使用哪种标题,政策都是按网页定义的: 您需要将 HTTP 标头连同您要发送的每个响应一起 以确保受到保护这提供了极大的灵活性 政策。假设有一组 您网站上的网页包含 +1 按钮,而其他网页则没有:您可以允许 按钮代码仅在必要时加载。
每条指令中的源列表都是灵活的。您可以指定来源
架构(data:
、https:
),或仅主机名指定范围
(example.com
,匹配该主机上的任何源站:任何架构、任何端口)
完全限定 URI(仅与 HTTPS 匹配,https://example.com:443
,
example.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-ancestors
、report-uri
或 sandbox
。
内嵌代码被视为有害代码
很明显,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-src
或 style-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 起的所有浏览器 非常安全 - 重写当前正在进行的任何
setTimeout
或setInterval
调用 使用内联函数代替字符串。例如:
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@ 邮寄名单归档, 或者您也可以自己加入。