内容安全策略

来源:互联网 发布:大智慧分析家软件 编辑:程序博客网 时间:2024/05/21 10:19

更多HTML 5文章请查阅HTML 6在线网站http://www.html5online.com.cn

本文概述
        在传统Web应用程序中,数据的安全性是通过同源策略来实现的。站点A中的代码只能访问站点A中的数据。每一个站点均处于孤立状态,从而保证每一个站点中数据的安全性。从理论上来说,这种做法是无懈可击的,但事实上,许多网络攻击者都已经聪明地发现了如何突破这种限制的方法。
        例如,跨站点脚本攻击,就是通过发送恶意代码欺骗站点的方式突破了这个同源策略限制。这是一个很大的问题,因为浏览器总是信任来自同一个站点中的所有页面上的代码。目前的跨站点脚本攻击主要采用通过注入恶意代码来突破这种安全策略的方法。如果攻击者成功注入一段恶意代码,则这段恶意代码将极有可能在客户端被直接执行,所以我们当然需要尽量避免这种情况。
        本文介绍一种新的防御方法—内容安全策略,该方法可以极大程度地降低这种风险,使现代浏览器最大程度地抵挡这些攻击者的恶意攻击。
代码白名单
        网络攻击者主要利用的弱点是浏览器不具有识别一段脚本代码是属于应用程序中所使用的脚本代码,还是被第三方恶意注入的脚本代码的能力。例如,当用户单击页面上的一个按钮时将执行该应用程序中某个脚本文件中的一段脚本代码,我们可以信任这段代码,但是浏览器并不能识别这段代码是否来自于我们的站点,因此浏览器将立即下载我们所请求的脚本文件(如果脚本代码被单独放置在脚本文件中且之前未被下载),执行我们在页面上发出的请求中所指定的脚本代码,而不管这段脚本代码究竟是什么内容。
        内容安全策略定义一个Content-Security-Policy HTTP头,它允许你为可信任内容创建一个代码白名单,通知浏览器只执行受信任的内容。这样的话即使一个攻击者可以找到一个漏洞并注入恶意代码,由于这些恶意代码不属于受信任内容,因此也不会被执行。
        例如我们信任来自apis.google.com站点向我们站点所发出的代码,同时我们当然也相信我们自己的站点中的所有代码,我们可以定义一个只允许执行来自这两个站点中的代码。
Content-Security-Policy: script-src 'self' https://apis.google.com
        在这行代码中,我们指定我们自己的站点是一个受信任的站点,而https://apis.google.com是另一个受信任站点。浏览器将只会下载与执行来自https://apis.google.com站点的脚本代码,以及我们自身站点的脚本代码。
        在使用了这个策略之后,当有脚本代码来自其他不受信任的站点时,浏览器将会简单抛出一个错误,而不是加载与执行该脚本代码。当一个高明的攻击者想要在你的站点中注入代码时,他将会见到一个错误信息。
可以针对各种资源使用内容安全策略
        可以通过内容安全策略的使用来控制一个页面中所允许加载的各种资源。前面你已经看见过了用于控制脚本代码的script-src标识符,其他可以控制的各种资源如下所示:
    connect-src:用于指定你可以通过XHR、WebSockets与EventSource连接的站点。
    font-src:用于指定可以提供Web Font(服务器端字体)的站点。例如,可以通过“font-src https://themes.googleusercontent.com”访问Google所提供的Web Font。
    frame-src:用于指定可以内置在IFrame中的站点。例如,“frame-src https://youtube.com”允许你将YouTube视频内置在IFrame中,但不允许内置来自其他站点的页面。
    img-src:用于指定页面中可以加载来自哪个站点的图片。
    media-src:用于指定页面中可以加载来自哪个站点的视频或音频。
    object-src:用于指定浏览器可以加载哪些插件。
    style-src:用于指定浏览器可以加载来自哪些站点的样式文件。
        在默认情况下,如果不对某种资源使用内容安全策略,则该资源不受任何限制。
        你可以通过指定default-src标识符来修改这种默认行为。该标识符用于定义任何未使用标识符来进行指定的资源。例如如果default-src标识符被定义为http://siteA.com,并且你未使用img-src标识符,则你只能加载来自http://siteA.com站点的图片。在之前的示例中,由于只使用了script-src标识符,所以其他图片、字体等资源均不受任何限制。
        你可以在HTTP头中使用多个标识符,中间使用分号进行分割,如果你想在一个标识符中指定多个站点,则必须使用“script-src https://host1.com https://host2.com”的形式,如果使用“script-src https://host1.com; script-src https://host2.com”,则host2.com站点将会被忽略。
        如果你想在应用程序中加载siteA.com站点中的任何资源,但是不想在Iframe中加载任何页面,也不想加载任何插件,则可以使用如下所示的代码:
Content-Security-Policy: default-src https://siteA.com; frame-src 'none'; object-src 'none'
实现内容安全策略
        虽然在前面所述的示例中,我们使用的是Content-Security-Policy,但是目前为止在各浏览器中,我们还需要使用各浏览器的供应商前缀。在Firefox浏览器中,应该使用X-Content-Security-Policy,在使用WebKit引擎的浏览器(包括Safari浏览器与Chrome浏览器)中,使用X-WebKit-CSP。在下文中,我们将继续使用标准的Content-Security-Policy,各浏览器最终也会统一使用这个标准的Content-Security-Policy,但是目前为止在各浏览器中使用时还是应该根据所用浏览器的不同而使用不同的供应商前缀。
        在实现内容安全策略时,你需要首先在客户端发送的HTTP头中添加使用Content-Security-Policy。在不同的页面中,你可以根据需要而修改Content-Security-Policy头信息中的内容。
        在各标识符的值中,你可以随意使用诸如“data:”、“https:”之类的协议。你可以只指定域名,例如siteA.com(这时该域名可以与任何协议、任何端口相匹配),也可以具体指定协议、域名与端口,例如https://siteA.com:443(只与http协议、siteA.com域名与443端口相匹配)。可以使用*通配符,但是必须为协议、域名或端口分别使用每一个*通配符,例如*://*.siteA.com:*与siteA.com域名下的所有子域名匹配(但是与siteA.com本身不匹配),与所有协议、所有端口匹配。
        在对各标识符的值进行指定的时候,可以使用如下所示的四个关键字:
    'none':不与任何内容相匹配,相当于禁止使用该类资源。
    'self':与当前源(顶级域名+端口号)相匹配,但是与子域名不匹配。
    'unsafe-inline':允许内嵌JavaScript脚本代码与CSS样式代码(后文中详述)。
    'unsafe-eval':允许把一个字符串当作一个JavaScript表达式一样去执行(后文中详述)。
        在使用这些关键字时必须为这些关键字添加单引号,例如script-src 'self'表示允许浏览器执行当前源中的脚本代码,而script-src self却表示允许执行来自self域名的脚本代码。
内嵌代码被认为是有害的
        应该说明的是,内容安全策略是以在代码白名单中指定受信任站点为基础的,它明确规定浏览器可以接受来自哪些站点的资源,拒绝来自哪些站点的资源。但是代码白名单不能解决一个很大的问题:攻击者们可以通过使用在页面上注入内嵌脚本的方法而发起攻击。如果一个攻击者直接在页面上注入恶意代码段,浏览器将不能识别这段恶意代码。内容安全策略通过禁止执行页面上的内嵌脚本代码来解决这个问题,这是唯一确保安全的办法。
        因此,在使用内容安全策略的时候,你需要将script标签中的代码移入外部脚本文件中,例如针对如下所示的一段代码:
<script>
function test() {
    alert('你好!');
}
</script>
<button onclick='test();'>测试</button>
        你需要修改成如下所示的形式:
<!-- index.html页面文件 -->
<script src='script.js'></script>
<button id='testBtn'>测试</button>
//script.js脚本文件
function test() {
    alert('你好!');
}
document.addEventListener('DOMContentReady', function () {
    document.getElementById('testBtn').addEventListener('click',test);
});
        除了可以使用内容安全策略之外,将内嵌脚本代码移入外部脚本文件中具有许多好处:内嵌脚本代码容易使页面文件的结构变得比较混乱,外部脚本文件可以被浏览器缓存,外部脚本文件具有更好的可读性,而且可以被压缩。
        内嵌的样式代码也是如此,所有使用style属性与使用style标签书写的样式代码都应该被移入外部样式文件中。
        如果你确实必须在页面中嵌入脚本代码或样式代码,你可以使用unsafe-inline标识符,但是尽量不要采取这种做法。禁止内嵌代码是内容安全策略所能提供的最好的安全措施,虽然将所有代码移入外部文件中可能会使我们多付出一部分精力与时间,但是从我们的应用程序的安全性角度上来说,这个付出是值得的。
关于eval
        即使一个攻击者不能直接注入脚本代码,他可能会通过将字符串转换为可执行脚本代码并按照他的意愿来执行这段脚本代码的方法来攻击你的站点。eval()、new Function()、setTimeout([string],...)与setInterval([string],...)都是可以被攻击者利用来将字符串转换为可执行恶意脚本代码的方法。因此,在默认情况下,内容安全策略禁止在这些方法中使用字符串。
        因此,在使用内容安全策略的时候,我们应该采取如下所示的一些方法:
        通过JSON.parse方法解析JSON对象,而不是使用eval方法。目前为止,所有浏览器均支持JSON.parse方法,且该方法是一个非常安全的方法。
        重写setTimeout方法与setInterval方法,在方法中不使用字符串,而使用内部函数。例如将如下所示的语句:
setTimeout("document.querySelector('a').style.display = 'none';", 10);
        修改为
setTimeout(function () {
        document.querySelector('a').style.display = 'none';
 }, 10);
        避免使用内嵌模板:许多模板库使用new Function()方法来提高程序运行时模板的生成速度。这是一种极好的动态编程方法,但是也提高了运行恶意代码的风险。如果你的模板允许被预编译,预先编译你的模板可以提高应用程序的运行速度,并且使你的模板变得比较安全。
        如果你的应用程序必须在eval方法、setTimeout方法或setInterval方法中使用字符串,可以使用'unsafe-eval'标识符,但还是最好不要采取这种做法。禁止在这些方法中使用字符串可以使攻击者在你的站点中执行未被授权的代码的操作变得非常困难。
报告
        内容安全策略可以阻止未受信任的代码或资源被页面加载,但有时将某些信息送回服务器端是非常有必要的,因为你可以根据这些信息迅速识别并解决任何允许注入恶意代码的BUG。为了实现这个目的,你可以通过report-uri标识符让浏览器将一个JSON格式的报告提交到某个目标URL地址。
Content-Security-Policy: default-src 'self'; ...; report-uri /my_csp_report_parser;
        这个报告的内容类似如下所示:
{
    "csp-report": {
        "document-uri": "http://myself.com/index.html",
        "referrer": "http://attacker.example.com/",
        "blocked-uri": "http://attacker.example.com/attacker.js",
        "violated-directive": "script-src 'self' https://apis.google.com",
        "original-policy": "script-src 'self' https://apis.google.com; report-uri http://myself.com/my_csp_report_parser"
    }
}
        在这个报告中,包含了一些帮助你跟踪一些违反了内容安全策略的操作(例如攻击者的一个攻击操作)的原因,包括在document-uri属性中指明了针对哪个页面发生了违反操作(例如攻击者所攻击的页面),在referrer属性中指明了该违反操作(或者说攻击操作)来自于哪个站点,在blocked-uri属性中指明了违反内容安全策略的资源(例如攻击者所使用的脚本文件),在violated-directive属性中指明了该违反操作违反了哪个标识符,在original-policy属性中指明了该页面所使用的完整内容安全策略。
只报告模式
        有时,你可能只需要监视你的应用程序的安全状态,而不是正式使用严格的内容安全策略,这时,你可以通知浏览器只对内容安全策略进行监视,报告违反内容安全策略的操作,但并不实际使用这些内容安全策略。这时,你需要使用Content-Security-Policy-Report-Only头信息,而不是使用Content-Security-Policy头信息。
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_csp_report_parser;
        当使用只报告模式时,内容安全策略将不会阻止任何不受信任的资源,但是当有违反了内容安全策略的操作出现时,将会向你指定的目标URL发送一个报告。你可以同时使用Content-Security-Policy头信息与Content-Security-Policy-Report-Only头信息。通过这种做法,你可以为一些标识符只使用只报告模式,而对一些标识符使用正式应用模式。
内容安全模式使用示例
        目前为止,在Chrome 16以上、Firefox 4以上与IE 10版本的浏览器中支持内容安全策略的使用。在诸如Twitter之类的很多站点中已正式使用内容安全策略。
        如果你想在你的站点中使用内容安全策略,首先需要确定你需要使用的所有资源。当你确定好之后,可以为这些资源制定一个内容安全策略。让我们来看一些实际示例,以帮助我们了解应该如何制定一个正确的内容安全策略。
示例1:使用其他站点所提供的公共部件
        Google的+1 button站点中包含了来自https://apis.google.com站点的脚本代码,内嵌了一个来自https://plusone.google.com的Iframe。为了使用该站点中的按钮,你需要在内容安全策略中包含这些站点,一个最低的内容安全策略为script-src 'self' https://apis.google.com; frame-src https://plusone.google.com。同时你需要确定Google提供的JavaScript脚本代码被放置在一个外部脚本文件中。
        Facebook的Like button站点中使用了许多选项。我建议将来自该站点中的页面放置在Iframe元素中,因为iframe元素可以受沙箱保护,使其独立于你的站点。为了使用来自Facebook的功能,需要在内容安全策略中使用frame-src https://facebook.com。请注意。Facebook提供一个相对URL地址://facebook.com。为了显式使用HTTPS协议,必须使用https://facebook.com。
        Twitter的Tweet button站点即使用脚本代码,又使用Iframe,且都隶属于https://platform.twitter.com站点,一个最低的内容安全策略为script-src 'self' https://platform.twitter.com; frame-src https://platform.twitter.com,同时将Twitter提供的JavaScript脚本代码放置在一个外部脚本文件中。
        在使用其他站点中所提供的公共部件时,内容安全策略的制定方法均与此类似。我们建议你将default-src标识符的值设定为'none',然后在浏览器控制台窗口中进行监视,然后决定当你需要让这些部件正常工作时,你需要使用到哪些资源。
        如果要使用多个公共部件,可以使用多个标识符,并将所有资源分类后合并放置在一个标识符中。例如如果你想同时使用上述三个站点中的公共部件,制定的内容安全策略如下所示。
script-src 'self' https://apis.google.com https://platform.twitter.com; frame-src https://plusone.google.com https://facebook.com https://platform.twitter.com
示例2:锁定
        假设你正在为银行开发一个Web网站,你想确保浏览器只能加载你明确指定的资源。这时,我们可以先通过将default-src标识符指定为'none'的方法(default-src 'none')来阻挡所有资源,然后再使用其他标识符来明确指定浏览器可以加载的资源。
        假设该银行网站从https://cdn.mybank.net站点加载所有图片文件、样式文件与脚本文件,并且通过XHR从https://api.mybank.com/站点获取各种数据,使用Iframe,但Iframe中的页面均为网站内部的本地页面,网站中不使用Flash,不使用服务器端字体,不使用其他任何资源。但我们可以制定的内容安全策略如下所示:
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'

原创粉丝点击