在最开始,阐述浏览器安全的时候,我们提到了浏览器的隔离措施,虽然浏览器试图通过内容隔离逻辑,来保证不同源的文件之间访问的控制,但是这样的策略仍然是非完全安全的,从这一部分开始,我们就开始探讨关于Web应用,特别是浏览器的各种安全特性。
同源策略Same-Origin Policy 其实规则非常直接:除非JavaScript所处的两个页面的协议,DNS域名,端口完全一致,否则两个独立的JavaScript运行环境不能访问彼此的DOM,其他任何跨域文档JavaScript DOM 访问都会失败。
这个同源策略看起来非常易于理解,但其中包含的问题也不少。
首先是DNS,这里的问题是,浏览器在判断同源的时候,只对DNS域名进行判断,而非IP地址,假如某个特定主机的IP地址发生了变化,就有可能造成漏洞产生,这种攻击行为叫做DNS 重绑定攻击。
我们再看这个URL,浏览器代码里包含有多套独立的URL 解析处理,如果HTTP 堆栈里对源的解析和JavaScript对源的判断产生了误差,就可能导致问题。在各种浏览器中,有可能产生不少由于URL 刻意变形导致的SOP 绕过漏洞。
同时对于IE浏览器早期版本,甚至在做同源检查的时候,可能会忽略端口,后果可想而知。
同源策略在一开始只是针对DOM 的,但是后来这个策略逐渐扩散到了保护从JavaScript的根对象开始的其他敏感数据。不过非同源的脚本还是可以这任意窗口或者框架使用location.assign(), location.replace()
同时,同源策略还存在一些障碍,比如无法隔离本来属于两个用户的网页(因为无法给每个用户单独分配域名),另一种情况,这个策略也会使得原本合法的同站点域名不能无缝交换数据。比如login.exp.com 和 payment.exp.com。当然,修复的方法广泛支持的是document.domain 和 postMessage()。
document.domain
javaScript 通过设置document.domain 来覆盖同源检查策略中的主机名匹配,不过只是允许具有相同顶级域名,比如exp.com 甚至是 .com。赋值形式如下:
|
|
但需要注意的是,一个设置了document.domain 的页面是不能访问一个没有设置的页面,这样有可能造成本来是同源的页面,反而变成了隔离,虽然有时候有人试图使用这条规则,或者是给两个同源页面设置不同的document.domain,但实际上这种伪隔离域仍然支持对另一个设定进行 javascript:URL 跳转动作。
另外,由于场景不清晰,本来只是打算让两个页面同源,但此时会招来另一个页面,通过设置domain 一样能够同源,这样,实际上就是告诉我们,设置domain,实际上是将整个域的安全,交给了整个域中最不安全的那一个了。
postMessage(…)
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
postMessage(data,origin)方法接受两个参数:
考虑一下场景,在payments.exp.com 的根路径 下有两个页面,需要把用户登录信息显示在该页面上。为达到这个目的,payments.exp.com 这个页面加载一个纸箱login.exp.com 的子框架。这个子框架发送以下指令:
|
|
只有当payment.exp.com 确认是指定的信任源,浏览器才会发送postMessage 消息出于根路径下的接收方也需要确认消息的正确性,这样就确保了整个机制的健壮。
与浏览器身份验证的交互
在了解完DOM 相关的同源策略后,我们应当记住同源策略无法和全局身份认证,SSL 状态,网络上下文环境,以及众多浏览器管理涉及安全的其他参数同步。这里有一个很绕的例子:
某站点的登陆表单有CSRF 的漏洞,攻击者首先在框架里嵌入目标此网站上的一个敏感页面,然后诱使受害者登录到该站一个由攻击者控制的账号里,然后执行目标网站其他应用上的跨站攻击。进入HTTP 的身份验证信息已经改变了,但是后一个步骤里的代码注入却会对之前加载的子框架具有完全的访问权限,也会导致数据被窃取。这是一个很绕的攻击过程,不过不难理解,正是由于DOM 的同源和这些身份认证,状态等信息无法同步的原因造成的。
XMLHttpRequest 对象用于在后台与服务器交换数据。
而这个机制的关键是他会用到浏览器的HTTP 堆栈及其组件,包括全局身份认证,缓存机制,持续会话等。XMLHttpRequest 的同源机制,和浏览器的默认同源机制差不多,不过因一点点不同,比如XMLHttpRequest.open()里设定的目标URL 地址必须与发起的页面完全同源。
用户对于发送的请求头域有绝对的控制,其优点就是,通过插入一些自定义的请求头,可以确认特定的请求来源。但是问题就是插入某些请求头则可能对目标服务器端或者代理服务器的解析产生影响,比如设定错误的Content-Length 可能导致攻击者在原该浏览器维护的HTTP 持续会话里偷偷加入第二个请求。
考虑下面的代码:
|
|
一般,面对这种风险的方式,就是,设置一个黑名单,比如Content-Length, Host, Referer ,而对譬如User-Agent,Cookie, Origin , If-Modified-Since 的处理却各有不同。
Web Storage 是浏览器中实现的一个简单的数据存储功能,目前有两个JavaScript对象, localStorage , sessionStorage。前者是可以进行持久存储的,后者则是绑定当前浏览器,提供临时的缓存机制。
Cookie 的默认有效范围是域名,但是并没有办法吧Cookie 范围限制在单个主机名上,可以吧Cookie 里的Domain 设置为当前主机名上,但实际上访问该主机名的子域名,也会认为是有效的。
所以就形成了一个有意思的现象,当我们完全不设置domain 的时候,反而在某些浏览器上会将Cookie 的范围局限在主机名之内,但对IE 来说确实无效的。
那么如何有效的设置范围呢,cookie 中使用的是path. 只有请求的路径与cookie 的path 参数吻合,才会发送cookie。
但实际上,无论cookie 如何设定,对于同源策略来说,是不会检查URL 路径的,JS 代码还是可以随意访问同意主机上的不同URL,那么cookie 的path实际上不能作为有效的安全边界。
另外还有两个标记,分别是httponly,secure 。前者通过设置,可以禁止document.cookie 访问页面的cookie, 后者则要求cookie 不能用于非加密通道。
这么看起来稳妥了很多,然而还是有问题的,因为这可以防止数据不被读取,但是无法防止不被覆盖,通过js代码,我们可以使某域名对应的Cookie 池发生溢出,然后设置新的不带secure 的cookie 。对此的解决办法是,将cookie 池内容隔离,分为Httponly cookie 和普通的cookie,但是如果cookie 允许名字相同,在碰到匹配请求的时候,这些cookie 会被浏览器一起发送。
如此,我们可以看到,cookie和同源机制实际上是会相互影响到的,比如前边提到的同源机制影响了cookie 的路径范围机制,而另一方面讲,cookie 更加严重的影响了同源策略,因为cookie 经常被用作身份认证信息,如果cookie信息被获取,就相当于绕过了同源策略。
域名限制的问题
由于Cookie 的有效范围设置,容易造成安全问题,那么避免cookie 的域名错误,就是需要解决的问题。目前支持的方法是,数点号的个数。一般化的要求是,只有处于具体域名的主机名范围内,才能设置该域名对应的Cookie, 这个域名包括至少2个或3个点号,才能避免出现诸如.com .edu vas.us 这样的形式,特别的,对于域名属于7种特定格式的定居于明,那么需要包含2个点,除此之外则是3个点,但问题在于域名设置各式各样,比如有.com, .com.cn 但还可以直接.cn ,所以浏览器们基本上都是使用了各自的的过滤方法来处理,但是,如今域名的形式越来越多,相信cookie 的这一机制也面临各种挑战。
Cookie 与 合法DNS 劫持
这个实际上在中国非常广泛,运营社对于不存在的域名,会返回其广告页。那如果这个看起来非常破的广告页,如果存在漏洞,那么利用漏洞,可以获得任意域的上下文环境执行权限。
内容隔离是浏览器为Web 安全竖起的一道长城,然而由于 Web 本身的种种特点,造成了这个长城有非常多的漏洞,能够让长城两端以各种形式自由出入,这里边,关于插件部分,被我跳过了,主要是因为这一块比较陌生。