第二篇 客户端脚本安全
第2章 浏览器安全
近年来随着互联网的发展,人们发现浏览器才是互联网最大的入口,绝大多数用户使用互联网的工具是浏览器。因此浏览器市场的竞争也日趋白热化。
浏览器安全在这种激烈竞争的环境中被越来越多的人所重视。一方面,浏览器天生就是一个客户端,如果具备了安全功能,就可以像安全软件一样对用户上网起到很好的保护作用;另一方面,浏览器安全也成为浏览器厂商之间竞争的一张底牌,浏览器厂商希望能够针对安全建立起技术门槛,以获得竞争优势。
因此近年来随着浏览器版本的不断更新,浏览器安全功能变得越来越强大。在本章中,我们将介绍一些主要的浏览器安全功能。
2.1 同源策略
同源策略(Same Origin Policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略的基础之上的,浏览器只是针对同源策略的一种实现。
对于客户端Web安全的学习与研究来说,深入理解同源策略是非常重要的,也是后续学习的基础。很多时候浏览器实现的同源策略是隐性、透明的,很多因为同源策略导致的问题并没有明显的出错提示,如果不熟悉同源策略,则可能一直都会想不明白问题的原因。
浏览器的同源策略,限制了来自不同源的“document”或脚本,对当前“document”读取或设置某些属性。
这一策略极其重要,试想如果没有同源策略,可能a.com的一段JavaScript脚本,在b.com未曾加载此脚本时,也可以随意涂改b.com的页面(在浏览器的显示中)。为了不让浏览器的页面行为发生混乱,浏览器提出了“Origin”(源)这一概念,来自不同Origin的对象无法互相干扰。
对于JavaScript来说,以下情况被认为是同源与不同源的。
浏览器中JavaScript的同源策略(当JavaScript被浏览器认为来自不同源时,请求被拒绝)
由上表可以看出,影响“源”的因素有:host(域名或IP地址,如果是IP地址则看做一个根域名)、子域名、端口、协议。
需要注意的是,对于当前页面来说,页面内存放JavaScript文件的域并不重要,重要的是加载JavaScript页面所在的域是什么。
换言之,a.com通过以下代码:
<script src=http://b.com/b.js ></script>
加载了b.com上的b.js,但是b.js是运行在a.com页面中的,因此对于当前打开的页面(a.com页面)来说,b.js的Origin就应该是a.com而非b.com。
在浏览器中,<script>、<img>、<iframe>、<link>等标签都可以跨域加载资源,而不受同源策略的限制。这些带“src”属性的标签每次加载时,实际上是由浏览器发起了一次GET请求。不同于XMLHttpRequest的是,通过src属性加载的资源,浏览器限制了JavaScript的权限,使其不能读、写返回的内容。
对于XMLHttpRequest来说,它可以访问来自同源对象的内容。比如下例:
<html> <head> <script type="text/javascript"> var xmlhttp; function loadXMLDoc(url) { xmlhttp=null; if (window.XMLHttpRequest) {// code for Firefox, Opera, IE7, etc. xmlhttp=new XMLHttpRequest(); } else if (window.ActiveXObject) {// code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } if (xmlhttp! =null) { xmlhttp.onreadystatechange=state_Change; xmlhttp.open("GET", url, true); xmlhttp.send(null); } else { alert("Your browser does not support XMLHTTP."); } } function state_Change() { if (xmlhttp.readyState==4) {// 4 = "loaded" if (xmlhttp.status==200) {// 200 = "OK" document.getElementById('T1').innerHTML=xmlhttp.responseText; } else { alert("Problem retrieving data:" + xmlhttp.statusText); } } } </script> </head> <body onload="loadXMLDoc('/example/xdom/test_xmlhttp.txt')"> <div id="T1" style="border:1px solid black; height:40; width:300; padding:5"></div><br /> <button onclick="loadXMLDoc('/example/xdom/test_xmlhttp2.txt')">Click</button> </body> </html>
但XMLHttpRequest受到同源策略的约束,不能跨域访问资源,在AJAX应用的开发中尤其需要注意这一点。
如果XMLHttpRequest能够跨域访问资源,则可能会导致一些敏感数据泄露,比如CSRF的token,从而导致发生安全问题。
但是互联网是开放的,随着业务的发展,跨域请求的需求越来越迫切,因此W3C委员会制定了XMLHttpRequest跨域访问标准。它需要通过目标域返回的HTTP头来授权是否允许跨域访问,因为HTTP头对于JavaScript来说一般是无法控制的,所以认为这个方案可以实施。注意:这个跨域访问方案的安全基础就是信任“JavaScript无法控制该HTTP头”,如果此信任基础被打破,则此方案也将不再安全。
跨域访问请求过程
具体的实现过程,在本书的“HTML 5安全”一章中会继续探讨。
对于浏览器来说,除了DOM、Cookie、XMLHttpRequest会受到同源策略的限制外,浏览器加载的一些第三方插件也有各自的同源策略。最常见的一些插件如Flash、Java Applet、Silverlight、Google Gears等都有自己的控制策略。
以Flash为例,它主要通过目标网站提供的crossdomain.xml文件判断是否允许当前“源”的Flash跨域访问目标资源。
以www.qq.com的策略文件为例,当浏览器在任意其他域的页面里加载了Flash后,如果对www.qq.com发起访问请求,Flash会先检查www.qq.com上此策略文件是否存在。如果文件存在,则检查发起请求的域是否在许可范围内。
www.qq.com的crossdomain.xml文件
在这个策略文件中,只有来自*.qq.com和 *.gtimg.com域的请求是被允许的。依靠这种方式,从Origin的层面上控制了Flash行为的安全性。
在Flash 9及其之后的版本中,还实现了MIME检查以确认crossdomain.xml是否合法,比如查看服务器返回HTTP头的Content-Type是否是text/*、application/xml、application/xhtml+xml。这样做的原因,是因为攻击者可以通过上传crossdomain.xml文件控制Flash的行为,绕过同源策略。除了MIME检查外,Flash还会检查crossdomain.xml是否在根目录下,也可以使得一些上传文件的攻击失效。
然而浏览器的同源策略也并非是坚不可摧的堡垒,由于实现上的一些问题,一些浏览器的同源策略也曾经多次被绕过。比如下面这个IE 8的CSS跨域漏洞。
www.a.com/test.html:
<body> {}body{font-family: aaaaaaaaaaaaaa bbbbbbbbbbbbbbbb </body>
www.b.com/test2.html:
<style> @import url("http://www.a.com/test.html"); </style> <script> setTimeout(function(){ var t = document.body.currentStyle.fontFamily; alert(t); },2000); </script>
在www.b.com/test2.html中通过@import加载了http://www.a.com/test.html为CSS文件,渲染进入当前页面DOM,同时通过document.body.currentStyle.fontFamily访问此内容。问题发生在IE的CSS Parse的过程中,IE将fontFamily后面的内容当做了value,从而可以读取www.a.com/test.html的页面内容。
在www.b.com下读取到了www.a.com的页面内容
我们前面提到,比如<script>等标签仅能加载资源,但不能读、写资源的内容,而这个漏洞能够跨域读取页面内容,因此绕过了同源策略,成为一个跨域漏洞。
浏览器的同源策略是浏览器安全的基础,在本书后续章节中提到的许多客户端脚本攻击,都需要遵守这一法则,因此理解同源策略对于客户端脚本攻击有着重要意义。同源策略一旦出现漏洞被绕过,也将带来非常严重的后果,很多基于同源策略制定的安全方案都将失去效果。
2.2 浏览器沙箱
针对客户端的攻击近年来呈现爆发趋势:
2009年全年挂马网站状况趋势图
这种在网页中插入一段恶意代码,利用浏览器漏洞执行任意代码的攻击方式,在黑客圈子里被形象地称为“挂马”。
“挂马”是浏览器需要面对的一个主要威胁。近年来,独立于杀毒软件之外,浏览器厂商根据挂马的特点研究出了一些对抗挂马的技术。
比如在Windows系统中,浏览器密切结合DEP、ASLR、SafeSEH等操作系统提供的保护技术,对抗内存攻击。与此同时,浏览器还发展出了多进程架构,从安全性上有了很大的提高。
浏览器的多进程架构,将浏览器的各个功能模块分开,各个浏览器实例分开,当一个进程崩溃时,也不会影响到其他的进程。
Google Chrome是第一个采取多进程架构的浏览器。Google Chrome的主要进程分为:浏览器进程、渲染进程、插件进程、扩展进程。插件进程如flash、java、pdf等与浏览器进程严格隔离,因此不会互相影响。
Google Chrome的架构
渲染引擎由Sandbox隔离,网页代码要与浏览器内核进程通信、与操作系统通信都需要通过IPC channel,在其中会进行一些安全检查。
Sandbox即沙箱,计算机技术发展到今天,Sandbox已经成为泛指“资源隔离类模块”的代名词。Sandbox的设计目的一般是为了让不可信任的代码运行在一定的环境中,限制不可信任的代码访问隔离区之外的资源。如果一定要跨越Sandbox边界产生数据交换,则只能通过指定的数据通道,比如经过封装的API来完成,在这些API中会严格检查请求的合法性。
Sandbox的应用范围非常广泛。比如一个提供hosting服务的共享主机环境,假设支持用户上传PHP、Python、Java等语言的代码,为了防止用户代码破坏系统环境,或者是不同用户之间的代码互相影响,则应该设计一个Sandbox对用户代码进行隔离。Sandbox需要考虑用户代码针对本地文件系统、内存、数据库、网络的可能请求,可以采用默认拒绝的策略,对于有需要的请求,则可以通过封装API的方式实现。
而对于浏览器来说,采用Sandbox技术,无疑可以让不受信任的网页代码、JavaScript代码运行在一个受到限制的环境中,从而保护本地桌面系统的安全。
Google Chrome实现了一个相对完整的Sandbox:
Google Chrome的Sandbox架构
IE 8也采取了多进程架构,每一个Tab页即是一个进程,如下是IE 8的架构:
IE 8的架构
多进程架构最明显的一个好处是,相对于单进程浏览器,在发生崩溃时,多进程浏览器只会崩溃当前的Tab页,而单进程浏览器则会崩溃整个浏览器进程。这对于用户体验是很大的提升。
但是浏览器安全是一个整体,在现今的浏览器中,虽然有多进程架构和Sandbox的保护,但是浏览器所加载的一些第三方插件却往往不受Sandbox管辖。比如近年来在Pwn2Own大会上被攻克的浏览器,往往都是由于加载的第三方插件出现安全漏洞导致的。Flash、Java、PDF、.Net Framework在近年来都成为浏览器攻击的热点。
也许在不远的未来,在浏览器的安全模型中会更加重视这些第三方插件,不同厂商之间会就安全达成一致的标准,也只有这样,才能将这个互联网的入口打造得更加牢固。
2.3 恶意网址拦截
上节提到了“挂马”攻击方式能够破坏浏览器安全,在很多时候,“挂马”攻击在实施时会在一个正常的网页中通过<script>或者<iframe>等标签加载一个恶意网址。而除了挂马所加载的恶意网址之外,钓鱼网站、诈骗网站对于用户来说也是一种恶意网址。为了保护用户安全,浏览器厂商纷纷推出了各自的拦截恶意网址功能。目前各个浏览器的拦截恶意网址的功能都是基于“黑名单”的。
恶意网址拦截的工作原理很简单,一般都是浏览器周期性地从服务器端获取一份最新的恶意网址黑名单,如果用户上网时访问的网址存在于此黑名单中,浏览器就会弹出一个警告页面。
Google Chrome的恶意网址拦截警告
常见的恶意网址分为两类:一类是挂马网站,这些网站通常包含有恶意的脚本如JavaScript或Flash,通过利用浏览器的漏洞(包括一些插件、控件漏洞)执行shellcode,在用户电脑中植入木马;另一类是钓鱼网站,通过模仿知名网站的相似页面来欺骗用户。
要识别这两种网站,需要建立许多基于页面特征的模型,而这些模型显然是不适合放在客户端的,因为这会让攻击者得以分析、研究并绕过这些规则。同时对于用户基数巨大的浏览器来说,收集用户访问过的历史记录也是一种侵犯隐私的行为,且数据量过于庞大。
基于这两个原因,浏览器厂商目前只是以推送恶意网址黑名单为主,浏览器收到黑名单后,对用户访问的黑名单进行拦截;而很少直接从浏览器收集数据,或者在客户端建立模型。现在的浏览器多是与专业的安全厂商展开合作,由安全厂商或机构提供恶意网址黑名单。
一些有实力的浏览器厂商,比如Google和微软,由于本身技术研发实力较强,且又掌握了大量的用户数据,因此自建有安全团队做恶意网址识别工作,用以提供浏览器所使用的黑名单。对于搜索引擎来说,这份黑名单也是其核心竞争力之一。
PhishTank是互联网上免费提供恶意网址黑名单的组织之一,它的黑名单由世界各地的志愿者提供,且更新频繁。
PhishTank的恶意网址列表
类似地,Google也公开了其内部使用的SafeBrowsing API,任何组织或个人都可以在产品中接入,以获取Google的恶意网址库。
除了恶意网址黑名单拦截功能外,主流浏览器都开始支持EV SSL证书(Extended Validation SSL Certificate),以增强对安全网站的识别。
EVSSL证书是全球数字证书颁发机构与浏览器厂商一起打造的增强型证书,其主要特色是浏览器会给予EVSSL证书特殊待遇。EVSSL证书也遵循X509标准,并向前兼容普通证书。如果浏览器不支持EV模式,则会把该证书当做普通证书;如果浏览器支持(需要较新版本的浏览器)EV模式,则会在地址栏中特别标注。
在IE中:
EV证书在IE中的效果
在Firefox中:
EV证书在Firefox中的效果
而普通的https证书则没有绿色的醒目提示:
普通证书在IE中的效果
因此网站在使用了EV SSL证书后,可以教育用户识别真实网站在浏览器地址栏中的“绿色”表现,以对抗钓鱼网站。
使用EV证书的网站在IE中的效果
虽然很多用户对浏览器的此项功能并不熟悉,EVSSL证书的效果并非特别好,但随着时间的推移,有望让EVSSL证书的认证功能逐渐深入人心。
2.4 高速发展的浏览器安全
“浏览器安全”领域涵盖的范围非常大,且今天浏览器仍然在不断更新,不断推出新的安全功能。
为了在安全领域获得竞争力,微软率先在IE 8中推出了XSS Filter功能,用以对抗反射型XSS。一直以来,XSS(跨站脚本攻击)都被认为是服务器端应用的漏洞,应该由服务器端应用在代码中修补,而微软率先推出了这一功能,就使得IE 8在安全领域极具特色。
当用户访问的URL中包含了XSS攻击的脚本时,IE就会修改其中的关键字符使得攻击无法成功完成,并对用户弹出提示框。
IE 8拦截了XSS攻击
有安全研究员通过逆向工程反编译了IE 8的可执行文件,得到下面这些规则:
{(v|(&[#()\[\].]x?0*((86)|(56)|(118)|(76)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13)|(10)|A|D) ;? ))*(b|(&[#()\[\].]x?0*((66)|(42)|(98)|(62)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13)|(10)|A |D); ? ))*(s|(&[#()\[\].]x?0*((83)|(53)|(115)|(73)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13)|(1 0)|A|D); ? ))*(c|(&[#()\[\].]x?0*((67)|(43)|(99)|(63)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13) |(10)|A|D); ? ))*{(r|(&[#()\[\].]x?0*((82)|(52)|(114)|(72)); ? ))}([\t]|(&[#()\[\].]x?0*( 9|(13)|(10)|A|D); ? ))*(i|(&[#()\[\].]x?0*((73)|(49)|(105)|(69)); ? ))([\t]|(&[#()\[\].]x ?0*(9|(13)|(10)|A|D); ? ))*(p|(&[#()\[\].]x?0*((80)|(50)|(112)|(70)); ? ))([\t]|(&[#()\[\ ].]x?0*(9|(13)|(10)|A|D); ? ))*(t|(&[#()\[\].]x?0*((84)|(54)|(116)|(74)); ? ))([\t]|(&[#( )\[\].]x?0*(9|(13)|(10)|A|D); ? ))*(:|(&[#()\[\].]x?0*((58)|(3A)); ? )).} {(j|(&[#()\[\].]x?0*((74)|(4A)|(106)|(6A)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13)|(10)|A|D) ;? ))*(a|(&[#()\[\].]x?0*((65)|(41)|(97)|(61)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13)|(10)|A |D); ? ))*(v|(&[#()\[\].]x?0*((86)|(56)|(118)|(76)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13)|(1 0)|A|D); ? ))*(a|(&[#()\[\].]x?0*((65)|(41)|(97)|(61)); ? ))([\t]|(&[#()\[\].]x?0*(9|(13) |(10)|A|D); ? ))*(s|(&[#()\[\].]x?0*((83)|(53)|(115)|(73)); ? ))([\t]|(&[#()\[\].]x?0*(9| (13)|(10)|A|D); ? ))*(c|(&[#()\[\].]x?0*((67)|(43)|(99)|(63)); ? ))([\t]|(&[#()\[\].]x?0* (9|(13)|(10)|A|D); ? ))*{(r|(&[#()\[\].]x?0*((82)|(52)|(114)|(72)); ? ))}([\t]|(&[#()\[\] .]x?0*(9|(13)|(10)|A|D); ? ))*(i|(&[#()\[\].]x?0*((73)|(49)|(105)|(69)); ? ))([\t]|(&[#() \[\].]x?0*(9|(13)|(10)|A|D); ? ))*(p|(&[#()\[\].]x?0*((80)|(50)|(112)|(70)); ? ))([\t]|(& [#()\[\].]x?0*(9|(13)|(10)|A|D); ? ))*(t|(&[#()\[\].]x?0*((84)|(54)|(116)|(74)); ? ))([\t ]|(&[#()\[\].]x?0*(9|(13)|(10)|A|D); ? ))*(:|(&[#()\[\].]x?0*((58)|(3A)); ? )).} {<st{y}le.*? >.*? ((@[i\\])|(([:=]|(&[#()\[\].]x?0*((58)|(3A)|(61)|(3D)); ? )).*? ([(\\]|( &[#()\[\].]x?0*((40)|(28)|(92)|(5C)); ? ))))} {[ /+\t\"\'`]st{y}le[ /+\t]*? =.*? ([:=]|(&[#()\[\].]x?0*((58)|(3A)|(61)|(3D)); ? )).*? ([ (\\]|(&[#()\[\].]x?0*((40)|(28)|(92)|(5C)); ? ))} {<OB{J}ECT[ /+\t].*? ((type)|(codetype)|(classid)|(code)|(data))[ /+\t]*=} {<AP{P}LET[ /+\t].*? code[ /+\t]*=} {[ /+\t\"\'`]data{s}rc[ +\t]*? =.} {<BA{S}E[ /+\t].*? href[ /+\t]*=} {<LI{N}K[ /+\t].*? href[ /+\t]*=} {<ME{T}A[ /+\t].*? http-equiv[ /+\t]*=} {<\? im{p}ort[ /+\t].*? implementation[ /+\t]*=} {<EM{B}ED[ /+\t].*? SRC.*? =} {[ /+\t\"\'`]{o}n\c\c\c+? [ +\t]*? =.} {<.*[:]vmlf{r}ame.*? [ /+\t]*? src[ /+\t]*=} {<[i]? f{r}ame.*? [ /+\t]*? src[ /+\t]*=} {<is{i}ndex[ /+\t>]} {<fo{r}m.*? >} {<sc{r}ipt.*? [ /+\t]*? src[ /+\t]*=} {<sc{r}ipt.*? >} {[\"\'][ ]*(([^a-z0-9~_:\'\" ])|(in)).*? (((l|(\\u006C))(o|(\\u006F))({c}|(\\u00{6}3)) (a|(\\u0061))(t|(\\u0074))(i|(\\u0069))(o|(\\u006F))(n|(\\u006E)))|((n|(\\u006E))(a|( \\u0061))({m}|(\\u00{6}D))(e|(\\u0065)))).*? =} {[\"\'][ ]*(([^a-z0-9~_:\'\" ])|(in)).+? {[\[]}.*? {[\]]}.*? =} {[\"\'][ ]*(([^a-z0-9~_:\'\" ])|(in)).+? {[.]}.+? =} {[\"\'].*? {\)}[ ]*(([^a-z0-9~_:\'\" ])|(in)).+? {\(}} {[\"\'][ ]*(([^a-z0-9~_:\'\" ])|(in)).+? {\().*? {\}}}
这些规则可以捕获URL中的XSS攻击,其他的安全产品可以借鉴。
而Firefox也不甘其后,在Firefox 4中推出了Content Security Policy(CSP)。这一策略是由安全专家Robert Hanson最早提出的,其做法是由服务器端返回一个HTTP头,并在其中描述页面应该遵守的安全策略。
由于XSS攻击在没有第三方插件帮助的情况下,无法控制HTTP头,所以这项措施是可行的。
而这种自定义的语法必须由浏览器支持并实现,Firefox是第一个支持此标准的浏览器。
使用CSP的方法如下,插入一个HTTP返回头:
X-Content-Security-Policy: policy
其中policy的描述极其灵活,比如:
X-Content-Security-Policy: allow 'self' *.mydomain.com
浏览器将信任来自mydomain.com及其子域下的内容。
又如:
X-Content-Security-Policy: allow 'self'; img-src *; media-src media1.com; script-src userscripts.example.com
浏览器除了信任自身的来源外,还可以加载任意域的图片、来自media1.com的媒体文件,以及userscripts.example.com的脚本,其他的则一律拒绝。
CSP的设计理念无疑是出色的,但是CSP的规则配置较为复杂,在页面较多的情况下,很难一个个配置起来,且后期维护成本也非常巨大,这些原因导致CSP未能得到很好的推广。
除了这些新的安全功能外,浏览器的用户体验也越来越好,随之而来的是许多标准定义之外的“友好”功能,但很多程序员并不知道这些新功能,从而可能导致一些安全隐患。
比如,浏览器地址栏对于畸形URL的处理就各自不同。在IE中,如下URL将被正常解析:
www.google.com\abc
会变为
www.google.com/abc
具有同样行为的还有Chrome,将“\”变为标准的“/”。
但是Firefox却不如此解析,www.google.com\abc将被认为是非法的地址,无法打开。
同样“友好”的功能还有,Firefox、IE、Chrome都会认识如下的URL:
www.google.com? abc
会变为
www.google.com/? abc
Firefox比较有意思,还能认识如下的URL:
[http://www.cnn.com] [http://]www.cnn.com [http://www].cnn.com ……
这些功能虽然很“友好”,但是如果被黑客所利用,可能会用于绕过一些安全软件或者安全模块,反而不美了。
浏览器加载的插件也是浏览器安全需要考虑的一个问题。近年来浏览器所重点打造的一大特色,就是丰富的扩展与插件。
扩展和插件极大地丰富了浏览器的功能,但安全问题也随之而来,除了插件可能存在漏洞外,插件本身也可能会有恶意行为。扩展和插件的权限都高于页面JavaScript的权限,比如可以进行一些跨域网络请求等。
在插件中,也曾经出现过一些具有恶意功能的程序,比如代号为Trojan.PWS.ChromeInject.A的恶意插件,其目标是窃取网银密码。它有两个文件:
"%ProgramFiles%\Mozilla Firefox\plugins\npbasic.dll" "%ProgramFiles%\Mozilla Firefox\chrome\chrome\content\browser.js"
它将监控所有Firefox浏览的网站,如果发现用户在访问网银,就准备开始记录密码,并发送到远程服务器。新的功能,也给我们带来了新的挑战。
2.5 小结
浏览器是互联网的重要入口,在安全攻防中,浏览器的作用也越来越被人们所重视。在以往研究攻防时,大家更重视的是服务器端漏洞;而在现在,安全研究的范围已经涵盖了所有用户使用互联网的方式,浏览器正是其中最为重要的一个部分。
浏览器的安全以同源策略为基础,加深理解同源策略,才能把握住浏览器安全的本质。在当前浏览器高速发展的形势下,恶意网址检测、插件安全等问题都会显得越来越重要。紧跟浏览器发展的脚步来研究浏览器安全,是安全研究者需要认真对待的事情。