Content Security Policy(CSP)简介
传统的web安全应该主要是同源策略(same origin policy)。A网站的代码不能访问B网站的数据,每个域都和其他的域相互隔离,给开发者营造了一个安全沙箱。理论上这是非常聪明的做法,但是实际执行过程中,攻击者使用了各种高招可以推翻这套保护。
XSS攻击者把恶意代码注入在网站常规数据里,这样就可以绕过浏览器的同源策略。浏览器相信来自安全域的所有代码。 XSS Cheat Sheet 是一个古老但是却很有代表性的攻击方式。一旦攻击者成功注入代码,那么基本上Game Over了,用户数据泄露无疑,我们迫切希望防止这种攻击的发生。
本教程指出一种全新的防御方式:Content Security Policy(CSP),这是HTML5带给我们的一套全新主动防御的体系,CSP可以有效的降低XSS攻击,当然首先要浏览器支持。
白名单
浏览器的主要问题就是无法区分脚本是来自自己的应用还是来自第三方攻击者的恶意注入,而这一点恰恰被攻击者利用。举个例子:Google 的 +1 按钮会加载并执行一个来自 https://apis.google.com/js/plusone.js 的脚本,我们信任该脚本,但是我们不能期望浏览器去区分来自api.google.com的脚本是安全的,来自apis.evil.example.com的就不安全。浏览器都会下载并且执行。CSP定义了 Content-Security-Policy HTTP头来为你的内容创建一个来源的白名单。浏览器只允许白名单域里的资源和代码执行。这样就算攻击者找到了一个漏洞注入脚本,脚本不在白名单里面照样无法执行。
以前面的那个例子来继续说明,因为我们信任 api.google.com的代码,我们可以这样定义我们的协议
Content-Security-Policy: script-src 'self' https://apis.google.com
so easy,顾名思义,script-src控制script标签相关的策略,上例中,我们指定了'self'和https://apis.google.com作为他的值,浏览器只会下载并执行本域和https://apis.google.com的脚本。
如果有其他的脚本,浏览器会抛出如下异常,任凭攻击者如何注入,他都只能得到下面的结果:
策略支持多种资源
除了script资源外这种最明显的注入外,CSP还提供了多种指令来控制各种资源的加载。用法和script-src类似,我们快速过一下这些指令:
- connect-src
限制使用XHR,WebSockets,和EventSource的连接源。
- font-src
指定字体的下载源。
- frame-src
指定frame可以嵌入的连接源。
- img-src
指定图片的加载源。
- media-src
指定video和audio的数据源。
- object-src
指定Flash和其他插件的连接源。
- style-src
指定link的连接源。和script-src类似。
默认如果你不指定这些指令的值,将允许所有的来源,例如 font-src:*和不写font-src是等价的。
当然你也可以重写默认值,只要使用default-src就可以。比如:指定了default-src:https://example.com 但是没有指定font-src,那么你的font-src也只能从https://example.com加载。在之前的例子中我们只指定了script-src,这说明我们可以从任何其他的地方加载image,font和其他资源。
你可以为你的web应用指定多个或者一个指令,只需要在HTTP头里面列出来这些值,不同的值之间用“;”来分隔。但是如果你写成 script-src https://host1.com;script-src:https://host2.com,那么第二个script-src将不起作用,正确的写法是:script-src https://host1.com https://host2.com 。
再举一个例子:如果你的静态资源全都来自一个CDN,并且你知道不会有frame和其他插件。你可以这样写
Content-Security-Policy:default-src https://cdn.example.net;frame-src 'none';object-src:'none'
细节实现
X-WebKit-CSP,X-Content-Security-Policy都是曾经的HTTP头,在现代浏览器(除了IE)中,都已经支持不带前缀的 Content-Security-Policy头了。
CSP策略要求你每一个页面请求都要带上Content-Security-Policy头,这提供了很多灵活性,你甚至可以根据条件判断什么时候需要加CSP策略,什么时候不要。
协议的 source list 同样很灵活,你可以指定协议(data:,https:),或者只指定主机名:(example.com,允许任意协议,任意端口),或者全限制(https://example.com:443)。甚至可以使用通配符, :// .example.com:*,匹配example的任何子域,任意协议和端口, 注意:example.com不允许使用通配符 。
下面四个关键词也可以使用在source list中:
- 'none'
什么都不匹配。
- 'self'
匹配当前的域,不包括子域。
- 'unsafe-inline'
允许inline JavaScript 和 CSS。
- 'unsafe-eval'
允许 eval,new Function等的执行。
这几个关键词需要用单引号引用起来,如果不引用,则会被认为是域名。
沙箱
还有一个值得一提的就是沙箱:sandbox。沙箱的作用有一点不一样,他主要用来限制页面能做什么操作,而不是可以加载哪些资源。如果指定了sandbox指令,页面会当做一个iframe页面来对待:必须同源,禁止表单提交等。这些内容超出了本文的范畴,如果需要了解,可以访问如下链接:“sandboxing flag set” section of the HTML5 spec.
内联代码有风险
CSP是基于白名单的,这种方式很清楚的可以区分哪些资源被认可,哪些不能。但是这种方式没法解决一个很大的问题:内联脚本注入。如果攻击者可以注入一段脚本:
1
2
|
<span
class
=
"tag"
><<span
class
=
"title"
>script><span
class
=
"javascript"
>sendMyDataToEvilDotCom();<span
class
=
"tag"
></<span
class
=
"title"
>script>
</span></span></span></span></span>
|
浏览器没有机制区分这是合理代码还是恶意代码,CSP解决这个问题的方式是: 禁止所有内联脚本 。不仅仅包括<script>标签里的值,还包括内联事件和javascript:[CODE]的方式,如果你的代码是这样写的,那么你需要做一些改造了。
例如:
1
2
3
4
5
6
7
|
<span
class
=
"tag"
><<span
class
=
"title"
>script><span
class
=
"javascript"
>
<span
class
=
"function"
><span
class
=
"keyword"
>function <span
class
=
"title"
>doAmazingThings<span
class
=
"params"
>(){
alert(<span
class
=
"string"
>
'YOU ARE AMAZING'
);
}
<span
class
=
"tag"
></<span
class
=
"title"
>script>
<span
class
=
"tag"
><<span
class
=
"title"
>button onclick=<span
class
=
"value"
>
"doAmazingThings();"
>AM I amazing?<span
class
=
"tag"
></<span
class
=
"title"
>button>
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
|
需要改成
1
2
3
4
|
<span
class
=
"comment"
><!-- amazing.html -->
<span
class
=
"tag"
><<span
class
=
"title"
>script src=<span
class
=
"value"
>
'amazing.js'
><span
class
=
"tag"
></<span
class
=
"title"
>script>
<span
class
=
"tag"
><<span
class
=
"title"
>button id=<span
class
=
"value"
>
'amazing'
>Am I amazing?<span
class
=
"tag"
></<span
class
=
"title"
>button>
</span></span></span></span></span></span></span></span></span></span></span>
|
// amazing.js
function doAmazingThings() { alert('YOU AM AMAZING!'); } document.addEventListener('DOMContentReady', function () { document.getElementById('amazing') .addEventListener('click', doAmazingThings); });
重写的代码更符合标准,CSP也可以很好的运行。外部资源可以更好的被缓存和压缩。这是良好的编码习惯。
内联样式(inline style)有同样的处理方式。
如果你坚决需要使用内联脚本或者样式,你可以使用'unsafe-inline'作为script-src和style-src的值,但我们不建议这么做。禁止内联脚本和样式是CSP提供的安全机制,是值得做出的妥协。
Eval
尽管攻击者不能直接注入脚本,她可能会使用一些字符串的混淆瞒混浏览器过关,比如setInterval([string],...),可能最终会导致一些恶意攻击代码的执行,CSP规避折中风险的方法就是彻底禁用。
- 使用原生JSON.parse,避免使用eval
原生的JSON.parse绝对安全,而且除了IE8以下之外,其他浏览器都已经支持的很好了。
-
重写setTimeout
setTimeout("document.querySelector('a').style.display='none';",10);
改写成
setTimeout(function(){ document.querySelector('a').style.display = 'none'; },10);
-
避免运行时的模板工具
许多模板库使用 new Function() 加速运行时模板的生成。这种做法虽然很赞,但是却带来了风险。有些库支持不带new Function的CSP版本,降级到不使用eval的版本。
如果你使用的模板支持预编译(Handlebars),那将比最快的运行时编译模板都快,并且安全。同样,如果你必须使用这类 text-to-javascript方法,加入'unsafe-eval'到script-src。不够不建议这样。
Reporting
CSP的阻止不受信任的资源在客户端执行是一个不错的机制,但是如果能传回服务端一些报告将更棒。这样你可以在第一时间定位一些攻击和注入。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" } }
报告包含大量信息可以帮你定位攻击,包括攻击发生的页面,referer,block-uri,以及违背的策略和你定义的策略。
Report-Only
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
这样可以不阻止脚本和其他资源的运行,仅仅向后台发送一份报告。
实际使用
CSP在Chrome 16+,Safari 6+,Firefox 4+上得到了很好的支持。在IE10上得到了部分支持。Twitter(Twitter’s case study)和Facebook都率先商用。
Use Case #1:Social media widgets
-
Google's +1 button 包括一段来自https://apis.google.com,和一个内嵌的iframe来自https://plusone.google.com. 一个最基本的policy就是:script-src https://apis.google.com; frame-src https://plusone.google.com.
-
Facebook's Like button 可以有多种实现方法,我推荐使用iframe
frame-src https://facebook.com
默认facebook 的 iframe 默认使用的是 //facebook.com,请显示的使用https,如果没必要尽量使用https。
Use Case #2 Lockdown
假设你在做一个银行的网站,你需要保证除了你自己的脚本,其他的资源严禁执行,那么首先你可以使用default-src 'none'。
银行的图片,样式和脚本都来自CDN:https://cdn.mybank.net, 并且 XHR to https://api.mybank.com/ 会拉取大量数据,Frame也有使用,不过只加载本域的页面,没有第三方域。网站上没有flash,fonts。那么一个严格的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; frame-src 'self'
Use Case #3 SSL Only
一个婚戒论坛管理员希望保证所有的资源都是要你管ssl来加载,但是他有大量内联脚本,全部改掉是不现实的。我们可以设计出如下的规则:
Content-Security-Policy: default-src https; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
未来
Content Security Policy 1.0 是 W3C Candidate Recommendation,浏览器很快就实现了。现在W3C正在起草 Content Security Policy 1.1值得关注的1.1的新特性有:
- 支持 inline script 和 style
虽然我们应当尽量避免使用inline script和style,但我们依然需要兼顾现有的老站点,所以1.1使用 nouces 和 hashes两套机制来确保内联的脚本和样式是安全的。
- 策略可以使用meta加入
支持使用meta标签加入CSP策略
1
2
|
<span
class
=
"tag"
><<span
class
=
"title"
>meta http-equiv=<span
class
=
"value"
>
"Content-Security-Policy"
content=<span
class
=
"value"
>
"[POLICY GOES HERE]"
>
</span></span></span></span>
|
你甚至可以使用javascript来注入一段meta CSP策略。
- DOM API
将来,你将可以使用javascript来查询和修改CSP策略,你可以指定不同的策略来应对不同的情况。
- 新的指令
一些新的指令会加入,包括:plugin-types 限制插件可以加载MIME类型。form-action 表单提交目标地址的限制。
此文是alan的翻译作品,原文链接:http://www.html5rocks.com/en/tutorials/security/content-security-policy/