https://www.html5rocks.com/en/tutorials/security/content-security-policy/
Content Security Policy (CSP)
一般来说,CSP作为黑客/白名单机制,用于扩展程序加载或执行的资源。
Web的安全模式植根于同一起源策略。代码https://mybank.com只能访问https://mybank.com数据,并且https://evil.example.com绝对不允许访问。每个起源与网络的其余部分保持隔离,为开发人员提供了一个安全的沙箱,用于构建和播放。在理论上,这是非常辉煌的。实际上,攻击者发现了颠覆系统的聪明方法。
例如,跨站脚本(XSS)攻击绕过同一起源策略,通过欺骗网站将恶意代码与预期内容一起发送。这是一个巨大的问题,因为浏览器将页面上显示的所有代码信任为该页面安全来源的合法部分。该XSS小抄是一个攻击者可能利用通过注入恶意代码来破坏这种信任的方法的旧有代表性的横截面。如果一个攻击者成功地注入了任何代码,那么它几乎是游戏过程:用户会话数据被泄露,应该保密的信息被过滤到坏人。我们显然希望如果可能的话。
本教程强调了一个有希望的新防御,可以显着降低XSS攻击在现代浏览器中的风险和影响:内容安全策略(CSP)。
来源白名单
XSS攻击利用的核心问题是浏览器无法区分旨在成为应用程序一部分的脚本以及第三方恶意注入的脚本。例如,本文顶部的Google +1按钮从https://apis.google.com/js/plusone.js本页面的上下文加载并执行代码。我们相信该代码,但我们不能指望浏览器找出自己的代码apis.google.com是真棒,而代码apis.evil.example.com可能不是。浏览器愉快地下载并执行页面请求的任何代码,无论来源如何。
CSP定义了HTTP头,而不是盲目信任服务器提供的所有信息,Content-Security-Policy可以允许您创建信任内容源的白名单,并指示浏览器仅从这些来源执行或呈现资源。即使攻击者可以找到一个注入脚本的孔,脚本也不会与白名单匹配,因此不会被执行。
由于我们信任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 。
通过定义此策略,浏览器将简单地抛出错误,而不是从任何其他源加载脚本。当一个聪明的攻击者确实设法将代码注入您的网站时,她会长期运行错误信息,而不是她期望的成功:
政策适用于各种资源
虽然脚本资源是最明显的安全风险,但CSP提供了一组丰富的策略指令,可以对页面允许加载的资源进行相当精细的控制。你已经看到了script-src,所以这个概念应该是清楚的。我们快速浏览其余的资源指令:
- base-uri限制页面<base>元素中可能出现的网址。
- child-src列出了工作人员和嵌入式框架内容的URL。例如:child-src https://youtube.com可以嵌入来自YouTube的视频,但不能从其他来源嵌入视频。使用此代替不推荐使用的frame-src指令。
- connect-src 限制您可以连接的来源(通过XHR,WebSockets和EventSource)。
- font-src指定可以提供网络字体的起源。Google的Web字体可以通过启用font-src https://themes.googleusercontent.com
- form-action列出从<form>标签提交的有效端点。
- frame-ancestors指定可以嵌入当前页面的源代码。该指令适用于<frame>,<iframe>,<embed>,和<applet>标签。此伪指令不能在<meta>标记中使用,仅适用于非HTML资源。
- frame-src弃用。使用child-src来代替。
- img-src 定义可以加载图像的起源。
- media-src 限制允许传送视频和音频的起源。
- object-src 允许控制Flash和其他插件。
- plugin-types 限制页面可能调用的插件种类。
- report-uri指定浏览器在违反内容安全策略时发送报告的URL。此指令不能在<meta>标签中使用。
- style-src是script-src样式表的对应物。
- upgrade-insecure-requests指示用户代理重写URL方案,将HTTP更改为HTTPS。此指令适用于需要重写的大量旧URL的网站。
默认情况下,指令是广泛的。如果您没有为指令设置特定的策略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按钮,而另外一些则不需要:只有在必要时才可以加载按钮代码。
每个指令中的源列表是相当灵活的。您可以通过scheme(data:,https:)指定源,或者以hostname-only(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(我们也会得到这个)。
这些关键字需要单引号。script-src 'self'(带引号)授权从当前主机执行JavaScript。script-src self(没有引号)允许来自名为“ self”(而不是当前主机)的服务器的JavaScript ,这可能不是您的意思。
沙箱
还有一个更直接的值得谈论:sandbox。这与我们所看到的其他人有所不同,而且页面可以采取的行动限制,而不是页面可以加载的资源。如果sandbox指令存在,因为虽然它被装载的内页将被视为iframe同一个sandbox属性。这可以在页面上产生广泛的影响:强制页面成为一个独特的起源,并防止表单提交等。这超出了本文的范围,但是您可以在HTML5规范的“沙盒标志集”部分找到有效沙箱属性的完整详细信息。
元标记
CSP首选的传递机制是一个HTTP头。但是,可以直接在标记中在页面上设置策略。使用具有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基于白名单,因为这是一个明确的方式来指示浏览器将特定的资源集合视为可接受的,并拒绝其余的。但是,基于Origin的白名单并不能解决XSS攻击造成的最大威胁:内嵌脚本注入。如果攻击者可以注入直接包含一些恶意的有效载荷(<script>sendMyDataToEvilDotCom();</script>)的脚本标签,则浏览器没有任何机制可以将其与合法的内联脚本标记区分开来。CSP通过完全禁止内联脚本来解决这个问题:这是唯一可以确定的方法。
此禁令不仅包括直接嵌入到script标签中的脚本,还包括内联事件处理程序和javascript:URL。您需要将script标签的内容移动到外部文件中,并替换javascript:URL和<a ... onclick="[JAVASCRIPT]">适当的addEventListener调用。例如,您可以从以下内容重写以下内容:
<script> function doAmazingThings() { alert('YOU AM AMAZING!'); } </script> <button onclick='doAmazingThings();'>Am I amazing?</button>
更像是:
<script src='amazing.js'></script> <button id='amazing'>Am I amazing?</button>
// amazing.js function doAmazingThings() { alert('YOU AM AMAZING!'); } document.addEventListener('DOMContentReady', function () { document.getElementById('amazing') .addEventListener('click', doAmazingThings); });
重写的代码具有上述优点,并且与CSP无关的许多优点; 这是最好的做法,不管你使用CSP。内联JavaScript将结构和行为整合到不应该的方式。浏览器缓存外部资源更容易,开发人员更易于理解,有利于编译和分类。如果您将代码移植到外部资源中,您将编写更好的代码。
内联样式以相同的方式处理:style属性和style标签都应整合到外部样式表中,以防止CSS启用的各种令人惊奇的巧妙数据exfiltration方法。
如果你真的,绝对必须有内联的脚本和风格,你可以通过'unsafe-inline'在一个script-src或者style-src指令中添加一个允许的源来启用它。您也可以使用随机数或有(见下文)。但请不要。禁止内联脚本是CSP提供的最大的安全胜利,禁止内联样式也会使您的应用程序更加硬化。在将所有代码移出所有代码之后,确保事情正常运行,这是一个很大的努力,但这是一个非常值得的折中。
如果你绝对必须使用它...
CSP Level 2通过允许您使用加密随机数(使用一次数)或散列值将特定内联脚本列入白名单,为内联脚本提供向后兼容性。虽然在实践中这可能很麻烦,但它是有用的。
要使用随机数,请将脚本标签赋给一个nonce属性。其值必须与受信任来源列表中的值相匹配。例如:
<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'
请记住,必须为每个页面请求重新生成随机数,并且它们必须是无法确定的。
哈希的工作方式大致相同。不要在脚本标签中添加代码,而是创建脚本本身的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或更高版本,您可以打开DevTools然后重新加载您的页面。“控制台”选项卡将包含每个内联脚本的正确sha256哈希的错误消息。
评估太
即使攻击者无法直接插入脚本,也可能会欺骗您的应用程序将其他惰性文本转换成可执行JavaScript并代表她执行。eval(),new Function(),setTimeout([string], ...),and setInterval([string], ...)是通过注射文本最终可能会执行一些意外的恶意所有矢量。CSP对这种风险的默认回应是毫不奇怪的是完全阻止所有这些向量。
这对构建应用程序的方式有以下几个方面的影响:
- 通过内置解析JSON JSON.parse,而不是依靠eval。自IE8以来,每个浏览器都提供原生JSON操作,而且它们完全安全。
- 使用内联函数而不是字符串重写您正在进行的任何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的兄弟对您的应用程序完全至关重要,您可以通过'unsafe-eval'在script-src指令中添加允许的源来启用它们。但是,再一次请不要。禁止执行字符串的能力使得攻击者在您的站点上执行未经授权的代码更加困难。
报告
CSP阻止客户端不受信任的资源的能力对用户来说是一个巨大的胜利,但是将某种通知发送回服务器是非常有帮助的,以便您可以识别和压缩任何可以在第一名。为此,您可以指示浏览器将POSTJSON格式的违规报告指定到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),该页面的引荐来源(引荐来源,请注意该密钥未拼写错误),违反的资源页面的policy(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:社交媒体小部件
- 谷歌的+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文件中。如果您有一个使用child-src的现有策略,则需要将其更改为child-src。
- Facebook的Like按钮有许多实现选项。我建议坚持使用iframe版本,因为它可以安全地从您网站的其余部分沙箱。这将需要一个child-src https://facebook.com指令才能正常运行。请注意,默认情况下,iframe代码Facebook提供加载相对URL //facebook.com。请更改明确指定HTTPS: https://facebook.com。没有理由使用HTTP,如果你不必。
- Twitter的Tweet按钮依赖于访问脚本和框架,两者均托管在https://platform.twitter.com(Twitter同样在默认情况下提供相对URL:请在本地复制/粘贴时编辑代码以指定HTTPS)。script-src https://platform.twitter.com; child-src https://platform.twitter.com只要您将JavaScript代码段提供给外部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,没有字体,没有任何东西。在这种情况下我们可以发送的最严格的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'
即使https:指定了default-src,脚本和样式指令也不会自动继承该源。每个指令将完全覆盖该特定类型资源的默认值。
未来
内容安全策略第2级是候选推荐。W3C Web应用安全工作组没有闲置,背部拍照; 工作已经开始在规格下一次迭代。下一个版本已经在积极发展。
如果您对这些即将到来的功能的讨论感兴趣,请浏览public-webappsec @邮件列表存档,或加入自己。