CSP的今世与未来
作者:负羽
一、从两个工具说起
最近Google又推出了两款有关CSP利用的小工具,其一为CSP Evaluator,这是一个能够评估你当前输入的CSP能否帮助你有效避免XSS攻击的工具,其用法非常简单,在输入框中输入你当前设置或将要设置的CSP值,选择需要验证的CSP版本,然后按下“CHECK CSP”即可。不知道CSP是什么的同学,可以看下阿里聚安全博客以前推送的一篇文章《Content Security Policy 入门教程》
下面的列表中会给出评估工具对你输入CSP的安全性评估,所有条目用不同颜色标记了可能的影响程度。并且每个条目都可以单击展开详情,以按照建议修复可能存在的缺陷。如图:
CSPEvaluator还存在一个Chrome插件版本,同样易于使用。在使用了CSP的网站上单击扩展图标就可以自动对当前页面的CSP进行评估。
另一款工具为CSP Mitigator,这款Chrome插件允许您将自定义CSP策略应用于应用程序。 它可以帮助您了解启用CSP的后果,识别与您的策略不兼容的应用程序部分,并指导您在部署前进行任何必要的更改。效果图在Chrome商店中有,这里就不再赘述了。
可以看到,Google仍然在不遗余力地推行CSP的发展与应用。在CSP Evaluator的页面中,我们看到Google相关团队进行了一项规模巨大的研究来对CSP的现状进行了深入的分析,并基于这些结果,剖析了CSP的优缺点,并对CSP未来的发展给出了结论。就让我们跟随这篇文章,来看看CSP在今世的表现与CSP的未来是否可以期待。(为了方便描述论文内容,以下会用第一人称来叙述)
二、庐山真面目 —— 何为CSP
为了研究CSP(Content Security Policy)对XSS攻击的防护作用,他们做了对CSP安全模型的首次深入分析,分析了CSP标准中对web缺陷的保护能力,帮助识别常见的CSP策略配置的可能错误,并且展示了三类能使CSP无效化的绕过方法。
这次研究所采用的材料基于从Google搜索的索引文件中所提取到的CSP策略,从语料库中提取了大约1060亿页的页面,其中39亿是受CSP保护的,其中确认了26,011个独立的策略。他们发现,由于策略配置错误和白名单条目不安全,这些策略中至少有94.72%无法缓解XSS攻击。基于这样的研究结果,他们建议在实践中部署CSP时,使用基于nonce的方法而不是传统的白名单。并且,他们提出了名为“strict dynamic”的新特性,这是当前在Chromium浏览器中实现的CSP3规范的一个新特性。以下会详细讲述为何要使用这种策略和特性。
首先,何为CSP?我们知道,内容安全策略(CSP)是一种声明机制,允许Web开发者在其应用程序上指定多个安全限制,由支持的用户代理(浏览器)来负责强制执行。CSP旨在“作为开发人员可以使用的工具,以各种方式保护其应用程序,减轻内容注入漏洞的风险和减少它们的应用程序执行的特权”。当前,CSP还处在快速的发展期,目前正在进行规范中的版本是CSP3,CSP标准由用户代理选择实现。例如,Chromium具有完整的CSP2支持,并且实现了CSP3的大部分工作草案,仅在某些情况下可能会落后于实验中的某些特性,而MozillaFirefox和基于WebKit的浏览器则刚刚获得了完整的CSP2支持。在实际使用中,CSP策略在Content-Security-Policy HTTP响应头或<meta>元素中提供。
CSP的能力可以分为三类:
1、资源加载限制。CSP的最广为人知和常用的方面是将各种子资源的加载限制到开发人员允许的一组源的能力,这组源称为源列表。 常用的指令是script-src,style-src,img-src和兜底的 default-src; 调节资源的指令的完整列表如下表1所示。作为特殊情况,script-src和style-src指令还有几个额外的配置选项可用; 这些选项允许对脚本和样式表进行更细粒度的控制。
2、基于URL的限制。某些类型的攻击不能通过管理子资源来防止,但与之类似,对于文档也需要有可以与之交互的可信来源的概念。 一个常见的例子是frameancestors指令,它定义了允许的框架来源,以防止点击劫持。 类似地,base-uri和form-action定义哪些URL可以是<base#href>和<form#action>元素的目标,以防止一些Post XSS攻击。
3、杂项限制和强化选项。由于在Web应用程序中缺乏能够启用安全限制的其他常见机制,CSP已经成为几个松散适用的安全功能的集合。这些功能包括block-all-mixed-content和upgrade-insecurerequests,它们可以防止HTTPS混合内容错误并帮助改进HTTPS支持; plugin-types能够限制允许的插件格式; 还有sandbox,它反映了HTML5沙箱框架的安全功能。
通过这些能力我们可以看出,目前的CSP提供了对三种类型漏洞的保护功能:
XSS:XSS攻击能在一个脆弱的应用程序中注入并执行不受信任的脚本(用script-src和object-src指令来进行保护)
Clickjacking:Clickjacking通过在攻击者控制的页面上覆盖隐藏的框架来迫使用户在受影响的应用程序中执行不想要的操作。(通过限制框架嵌入和 frame-ancestors指令来保护)
Mixed content:Mixedcontent意味着在通过用HTTPS传递的页面上使用不安全协议加载资源(使用upgrade-insecure-requests和blockall-mixed-content关键字进行保护,限制将脚本和敏感资源加载到https网页中)
三、曲径通幽处 —— 如何绕过CSP
在谈论CSP带来的好处之前,我们需要注意到。由于一些比较受欢迎的用户代理(浏览器)还不支持或者仅对CSP提供部分支持,所以如果应用仅仅依赖CSP作为深度防御的手段会很容易让安全机制完全失败。因此,在使用CSP之外还必须采用传统保护机制; 例如,在生成HTML代码时使用带有严格上下文转义的框架,使用X-Frame-OptionsHeader以防止点击劫持,并确保资源在安全页面上通过HTTPS协议获取。请务必记住,设置CSP的实际好处是,当主要的安全机制不够用时,CSP可以在开发人员引入编程错误时保护用户远离可能导致的XSS,点击劫持或者混合内容错误。
事实上,由于点击劫持可以通过X-Frame-Options来避免,而混合内容错误在现代浏览器中已经默认被阻止了。因此CSP作为标准被提出的最主要目的,就是用于防护XSS攻击。而XSS,也正是既能通过CSP来缓解,又是开发者经常容易引入应用的错误。在实现用CSP防止不必要的脚本执行时,我们制定的策略必须满足三个要求:
1、策略必须同时定义script-src和object-src(或者使用default-src来补全),在缺少任意一项时均可以被绕过:
2、script-src的源列表不能包含unsafe-inline关键词(除非使用nonce)或者允许data: URI,否则可以被绕过:
3. script-src和object-src源列表不能包含含有攻击者可控制response的安全相关部分的源地址,或包含不安全的库。否则可以被绕过:
如果以上任意一条没有被满足的话,整个CSP对于XSS的防护就会彻底失效。
由于CSP的基本假设之一就是在策略白名单中的域名只会提供安全的内容,因此从理论上来说攻击者不应该能够将有效的JavaScript注入到白名单里来源的响应中。然而在实践中,我们发现现代web应用程序往往会因为几种模式违反这个假设。
1、JavaScript带有用户可控的回调:某些JSONP函数接受用户传递的函数名,但未做严格的过滤,导致可以注入任意JavaScript代码执行,即使做了基本的过滤,也可能使用SOME攻击来注入任意函数名:
2、反射或者符号执行:CSP对于脚本执行的限制可能(通常是意外地)被白名单中的脚本所规避。例如,脚本可以使用反射来查找和调用全局作用域中的函数,如图:
通常情况下这些函数不会有太大危害,因为参数是开发人员所控制的。然而当函数从DOM获取数据,而应用又有DOM注入的漏洞,则很可能被攻击者完全控制函数及其参数。一个典型的例子是目前流行的AngularJS库,它具有强大的模板语法和客户端模板执行:
默认情况下AngularJS会通过eval来执行代码,在特定的不允许eval的CSP场景下,AngularJS也支持使用“CSP compatibility mode” (ng-csp)来执行模板的代码。攻击者只需要从白名单中的域名里引入AngularJS,就可以在页面上通过注入ng-app标签来编写能够执行任意JavaScript代码的模板。
3、意料之外的可被当做的JavaScript来解析的响应:通过浏览器对MIME的检查宽松来执行任意js,比如伪造脚本响应类型。如果被黑客控制响应的话,在白名单中script-src和object-src都可能存在隐患。
4、作为安全手段的路径限制:CSP2中的whitelist可以指定路径(目录),然而如果白名单中的条目包含30X重定向,且能被黑客控制,则可以绕过CSP。
最常见于OAuth中,或被用于防止referer丢失。
四、古来圣贤皆寂寞 —— CSP的应用现状
为了找出互联网中有多少CSP策略能够切实有效地防护XSS攻击,Google团队构造了一些数据集并进行了标准化处理。根据我们以上的结论,想要达到好的XSS防护效果,CSP的XSS保护策略必须处在强制模式下,并且至少包含以下两个指令之一:script-src或default-src。因此可以据此确定含有XSS防护的CSP策略。
对于策略的安全性检查,他们采用了如下几种方式检验:
1、对于”unsafe-inline”的使用:对于一个使用了“unsafe-inline”并且没有使用nonce的策略,可以认为是很容易被绕过的。
2、缺少“object-src”:一个指定了“script-src”策略但是没有指定“object-src”(同时也没有“default-src”)的策略,是很容易用plugin绕过的。
3、在白名单中使用通配符:在白名单中使用通配符,或者使用URI Scheme很容易引起包含任意域名内容。
4、白名单中包含不安全的来源:如果白名单中的来源可能存在CSP Bypass,则也是不安全的。
对于如何检查存在CSP bypass的域名,我们定义为:
1、托管了AngularJS的域名,因为AngularJS允许在模板中执行任意代码。
2、暴露了JSONP接口的域名(可以使用JSONP任意定义js代码,或者SOME攻击自定义函数名)。
在进行了一系列数据的处理和分析之后,他们得到的结论是。在数据集中,有3,913,578,446(3.7%)个网址采用了CSP。 所有主机名中的1,664,019(0.16%)采用了CSP。
他们将整个数据集分为3类:
1、所有策略
2、XSS-Protection 策略(至少含有script-src,object-src或者default-src之一)
3、严格XSS-Protection策略(在2的基础上,不含unsafe-inline,unsafe-eval等,且URI Scheme,或者白名单通配符)
在此基础上,得到了目前互联网上CSP安全的总览:
1、所有策略数据中的94.72%并没有提供XSS防护
2、在所有提供XSS防护的数据中,有94.68%可以被绕过
3、在所有提供XSS防护的数据中,有87.3%指定了unsafe-inline并且没有部署nonce
4、在所有提供XSS防护的数据中,有9.4%既没有指定object-src,也没有default-src
5、在所有提供XSS防护的数据中,有21.48%使用了通配符或者URI Scheme
6、在忽略3,5的情况下,仍有51.05%的可以被绕过。有些是因为4,更多的是因为script-src中的白名单中包含了不安全的域
通过对于所得到的对XSS有效的CSP防护策略的分析,我们得到了白名单的安全性如下:
1、白名单数量越大,越难保证JSONP和AngularJS造成的安全问题
2、大概在严格策略中的41.65%,所有XSS防护策略中的79.17%,包含不安全的白名单
3、在使用12条白名单时,绕过率达到了94.8%
4、作为结果,我们得出的结论是,部署传统的基于白名单的CSP模型中防止XSS是不可行的,因为在实践中脚本执行限制通常可以被推翻。
可以看出,script-src的白名单是造成CSP失效的最大原因(除了没配置好的unsafe-inline和缺少object-src之外)。因此,我们应该如何改进CSP呢。其实CSP已经提供了更精细的方法来对信任脚本:加密随机数(cryptographic nonce)和哈希(hash)。 特别是nonces允许开发人员明确地注解每个受信任的脚本(不论内联和外部),同时禁止攻击者注入的脚本执行。 为了提高CSP的整体安全性,Google据此提出了一种稍微不同的策略写法。 应用程序维护者应该应用基于nonce的保护方法,而不是依赖白名单。 以下列表描述了基于白名单的CSP策略和满足此策略的脚本:
可以看出,白名单中包含了一个不安全的域名,因此所述CSP的策略就是不安全的。攻击者可以通过JSONP暴露出的点来注入恶意代码: https://example.org/script?callback=malicious_code.
五、江山代有才人出 —— 全新的CSP策略
为了防止这种事情的发生,我们使用nonce来定义策略。在nonce中,应用程序定义并生成了单一的,不可猜测的令牌(nonce),这个令牌会同时传递给CSP策略和作为一个合法HTML属性传递给script。 用户代理仅允许执行那些nonce值能够匹配策略中指定的值的脚本。虽然攻击者可以将标记注入易受攻击的页面,但是由于不知道nonce的临时值,因此他并不能执行恶意脚本。
通过使用nonce,可以单独将脚本列入白名单。 即使攻击者能够定位XSS,nonce的值也是不可预测的,因此攻击者不可能向JSONP注入有效脚本。而且由于浏览器支持多个策略,因此可以把nonce和whitelist一起写,用逗号隔开。nonce可以用来将各个脚本列入白名单,而whitelist可以用于集中实施安全策略。
那么,当我们生成动态脚本并插入时会发生什么呢?因为新生成的,被插入到页面的js并不知道nonce的值,所以会被拦截,因此需要CSP3里的 script-src: ’strict-dynamic’。 ’strict-dynamic’允许将信任关系传递给动态生成的脚本,也就是说,“strict-dynamic”允许js动态添加的脚本执行,而忽略script-src的白名单。并且,其他的script-src白名单会被忽略,浏览器不会执行静态或解析器插入的脚本,除非它伴随有效的nonce值。这里的关键点是,使用createElement()来插入js时,能够执行createElement()的js已经被信任了,并且黑客不知道nonce的时候无法注入恶意脚本。因此我们可以信任新建的js代码。举例,我们可以这么写CSP:
使用这样的策略时,开发者将需要向静态<script>元素添加nonce,但是会确保只有这些受信任的脚本及其后代才会执行脚本。这种部署CSP的模式可以显著地提高策略的安全性并促进大家使用。
当然,Nonce-based 策略并不是XSS的灵丹妙药,它也有它的局限性。
首先,从安全性来说:
1、如果XSS的根本原因是将URI中的不受信任数据传递到script的src属性中,并且通过createElement创建,那么strict-dynamic策略将允许其执行,反而是传统的whitelist会阻止它。
2、如果在noncedscript标签中有注入点,那么攻击者仍然可以执行攻击。(这点传统CSP也一样)
3、Post-XSS/scriptlessattacks,仍然无法防御。
而从兼容性角度来说:
1、对于解析器插入的脚本:如果使用document.write()来插入脚本,会被strict-dynamic策略阻止。使用者必须使用createElement()或者在document.write()创建的脚本中显式传递一个nonce
2、内联事件处理程序:strict-dynamic不会花费时间去移除和CSP不兼容的标记(例如javascript: 开头的URL,或者内联的事件处理程序),开发者需要自己去重构。
3、针对Google内部数据集的几百个XSS案例的分析表明绝大部分的XSS攻击都能被 nonce-based策略所减轻,并且这种策略明显比白名单更容易被开发人员接受。
六、以一片希望结尾
最后,总结一下Google这次调研的结论。他们对CSP的安全模型进行了深入分析,并确定了其中看似安全的策略没有提供安全改进。他们在超过10亿个主机名中调查了CSP的采用情况,并在Google搜索索引中确定了160万个主机在使用26,011个独特的策略。通过自动化检查,发现所有策略中的94.72%能被轻易绕过。所有策略中的75.81%和严格策略中的41.65%在白名单中也至少包括一个不安全主机。正是这些数字让我们相信白名单是不切实际的。
因此,Google提出一个新的策略方式。建议使用单个脚本的CSP nonces代替整个主机列入白名单的方式。并且,为了简化基于nonce的CSP,提出了strict-dynamic关键字。启用了这个关键字的网站在浏览器中会继承nonce到动态脚本中。因此,一个受nocne信任的脚本在运行中所执行的新脚本是被信任的。
虽然这种技术脱离了传统的主机白名单方法的CSP,但他们认为可用性改进足够大到足以证明其广泛采用。希望基于nonce的方法的组合和'strict-dynamic'关键字将允许开发人员和组织最终能够享受由内容安全策略提供的真正的安全利益。
在看到Google煞费苦心的这么多帮助推广CSP部署的工具,以及认真的深度调研后,我坚信CSP的今世可能还未能尽如人意,但是未来在Google以及其它各家标准制定者的大力推动下,改进后的CSP必能够在现代浏览器的XSS防御中大放异彩。
作者:负羽,更多安全类文章,请访问阿里聚安全博客