摘要
如今的WEB标准纷繁复杂,在浏览稍大一些的网站时,细心的人会发现网页上呈现的内容并不仅限于网站自身提供的内容,而是来自一堆五花八门的网站的内容的集合。但有谁会想到同源策略在保护着网民们的安全呢?
了解同源策略是十分有必要的,要深入掌握XSS / CSRF等WEB安全漏洞,不了解同源策略就如同盲人摸象一般,无法说出全貌,更无法应用其进行打击。另外,无论是网银盗号,还是隐私泄露,理解了同源策略,就有助于理解自己面临着什么样的威胁。
0x00 什么是源和同源策略
源就是主机,协议,端口名的一个三元组。
同源策略(Same Origin Policy, SOP)是Web应用程序的一种安全模型,它控制了网页中DOM之间的访问。重要的事情说三遍,它只是个模型,而不是标准(哪怕标准在实现的时候也会千差万别)。同源策略被广泛地应用在处理WEB内容的各种客户端上,比如各大浏览器,微软的Silverlight,Adobe的Flash/Acrobat等等。SOP影响范围包括:普通的HTTP请求、XMLHttpRequest、XSLT、XBL。
0x01 如何判断同源
定义:给定一个页面,如果另一个页面使用的协议、端口、主机名都相同,我们则认为两个页面具有相同的源。 举个例子,我们假设需要比对的目标页面来自这个URI: http://sub.eth.space/level/flower.html
0x02 同源策略究竟限制了什么?
首先,我们要明确同源策略只作用在实现了同源策略的WEB客户端上。 虽然笔者不常用百度,但是我们来看一个具有误导性的结论:百度词条对于同源策略的解释说“只有和目标同源的脚本才会被执行”,这是不对的,同源策略没有禁止脚本的执行,而是禁止读取HTTP回复。 更正了这个概念之后,我们会发现,SOP其实在防止CSRF上作用非常有限,CSRF的请求往往在发送出去的那一瞬间就已经达到了攻击的目的,比如发送了一段敏感数据,或请求了一个具体的功能,是否能读取回复并不那么重要(唯一的作用是可以防止CSRF请求读取异源的授权Token)。 另外,一般静态资源通常不受同源策略限制,如js/css/jpg/png等。
0x03 跨源的网络访问
为什么要起这么拗口的名字:网络访问?因为WEB上资源访问的多样性,不能简单的称之为网络请求。大体看来我们有三种类型:
跨域写,通常被允许,例如链接,重定向和表单提交,一些不常见的HTTP请求方法例如PUT,DELETE等需要先发送预请求(preflight),例如发送OPTIONS来查询可用的方法。
跨域嵌入,通常被允许。
跨域读,通常被禁止,然而,我们可以用其他方法达到读取的效果。
一个经久不衰的BUG
早在2011年,一个用户在Mozilla的Bug追踪系统中就提交了一个issue,声称他可以判定某个网站的访客是否登录了gmail,facebook等等。
嵌入iframe来获取一个访问网站的用户是否登陆了gmail:
-
<ol
class=
"linenums">
-
<li class="L0">
-
<span class="tag">
-
<img</span>
-
<span class="pln">
</span>
-
<span class="atn">style
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"
</span>
-
<span class="pln">display
</span>
-
<span class="pun">:
</span>
-
<span class="pln">none
</span>
-
<span class="pun">;
</span>
-
<span class="atv">"
</span>
-
<span class="pln">
</span>
</li>
-
<li class="L1">
-
<span class="pln">
</span>
-
<span class="atn">onload
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"
</span>
-
<span class="pln">logged_in_to_gmail
</span>
-
<span class="pun">()
</span>
-
<span class="atv">"
</span>
</li>
-
<li class="L2">
-
<span class="pln">
</span>
-
<span class="atn">onerror
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"
</span>
-
<span class="pln">not_logged_in_to_gmail
</span>
-
<span class="pun">()
</span>
-
<span class="atv">"
</span>
</li>
-
<li class="L3">
-
<span class="pln">
</span>
-
<span class="atn">src
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"https://mail.google.com/mail/photos/img/photos/public/AIbEiAIAAABDCKa_hYq24u2WUyILdmNhcmRfcGhvdG8qKDI1ODFkOGViM2I5ZjUwZmZlYjE3MzQ2YmQyMjAzMjFlZTU3NjEzOTYwAZwSCm_MMUDjh599IgoA2muEmEZD"
</span>
</li>
-
<li class="L4">
-
<span class="tag">/>
</span>
-
</li>
</ol>
src的代码试图访问一张gmail中攻击者上传的图片,如果用户没有登陆gmail,就无法成功加载。从而达到判断用户是否登陆gmail的效果。这种方法可以推广到任何对不应跨源访问的资源没有正确设置同源策略的网站。
0x04 谈谈攻击
作为一个抽象的WEB安全模型,每个处理WEB内容的客户端实现的都不完全一样,这就带来了许多差异点,而差异点的存在就带来了漏洞,正所谓“千里之堤,溃于蚁穴”。
对URI的解析
IP是URI的重要组成部分,如果留心了RFC的人就会知道,IP不止有一种格式。下面的标注形式其实都代表了同一个IP:216.58.209.68,大家可以在浏览器里实验。
-
<ol
class=
"linenums">
-
<li
class=
"L0">
-
<span
class=
"lit">
216.58</span>
-
<span
class=
"pun">.</span>
-
<span
class=
"lit">
53572</span>
-
</li>
-
<li
class=
"L1"><span
class=
"lit">
0xD8</span>
-
<span
class=
"pun">.</span>
-
<span
class=
"lit">
072.53572</span>
-
</li>
-
<li
class=
"L2">
-
<span
class=
"lit">
3627733316</span>
-
</li>
-
<li
class=
"L3">
-
<span
class=
"lit">
0330.3854660</span>
-
</li>
-
</ol>
当某些浏览器对URI的解释存在漏洞的时候,就可以构造出有趣的攻击链来绕过SOP。 比如CVE-2015-7188火狐浏览器SOP绕过中。攻击者构造了特殊的URL,并在攻击者自己控制的来自37.187.18.85的网页中发起跨域请求
先通过\0B让Firefox认为这个请求是请求37.187.18.85本身的内容,再通过类似@字符的Unicode字符@(\uFF20)让浏览器认为@之前的字符都是translate.google.com的账号和密码,从而返回translate.google.com的网页内容,实现绕过SOP。
设计缺陷导致SOP绕过
在Java6,7中,如果两个域名解析到相同的IP,则会认为他们同源。假设我们有attacker.com和victim.com,两者都共享主机123.123.123.123。攻击者attacker.com可以在自己控制的域名下上传一个jar文件来访问victim.com的内容。
访问本地文件的同源策略
不同的浏览器使用不同的浏览器引擎,而不同的引擎对于同源策略的处理也并非完全一致。例如,Firefox使用Gecko浏览器引擎,在古老的Gecko1.8版(Firefox3)之前,任意两个file://的URI都被认为是同源的,意思就是,任意本地HTML文件将有权限访问本地计算机上任意其他文件。如今的Gecko版本中,一个HTML文档只能访问其所在文件夹下的其他文件。 对于跨越窗口的DOM访问,每个文件被当作一个单独的源,除了一种情况:当一个文件被另一个文件可以用同源策略访问时,视为相同的源。
-
<ol class="linenums">
-
<li class="L0">
-
<span class="com">
<!-- 文件路径:/home/user/1.html -->
-
-
</span>
-
<span class="pln">
</span>
-
</li>
-
<li class="L1">
-
<span class="tag">
-
<html>
-
</span>
-
<span class="pln">
-
</span>
-
</li>
-
<li class="L2">
-
<span class="tag">
-
<frameset</span>
-
<span class="pln">
</span>
-
<span class="atn">cols
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"50%,*"
</span>
-
<span class="tag">>
</span>
-
<span class="pln">
</span>
-
</li>
-
<li class="L3">
-
<span class="pln">
</span>
-
<span class="tag">
<frame</span>
-
<span class="pln">
</span>
-
<span class="atn">src
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"/home/user/dir/2.html"
</span>
-
<span class="tag">>
</span>
-
</li>
-
<li class="L4">
-
<span class="pln">
</span>
-
<span class="tag">
-
<frame</span>
-
<span class="pln">
</span>
-
<span class="atn">src
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"..."
</span>
-
<span class="tag">>
</span>
-
</li>
-
<li class="L5">
-
<span class="tag">
-
</frameset>
</span>
-
<span class="pln">
</span>
-
</li>
-
<li class="L6">
-
<span class="tag">
-
</html>
-
</span>
-
</li>
-
</ol>
上图中,1.html和2.html被视为相同的源。
-
<ol
class=
"linenums">
-
<li class="L0">
-
<span class="com">
<!-- 文件路径:/home/user/dir/1.html -->
-
-
</span>
-
<span class="pln">
</span>
-
</li>
-
<li
class=
"L1">
-
<span class="tag">
-
<html>
-
</span>
-
<span class="pln">
</span>
-
</li>
<li class="L2">
-
<span class="tag">
-
<frameset</span>
-
<span class="pln">
</span>
-
<span class="atn">cols
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"50%,*"
</span>
-
<span class="tag">>
</span>
-
<span class="pln">
</span>
-
</li>
<li class="L3">
-
<span class="pln">
</span>
-
<span class="tag">
<frame</span>
-
<span class="pln">
</span>
-
<span class="atn">src
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"/home/user/2.html"
</span>
-
<span class="tag">>
</span>
-
</li>
-
<li class="L4">
-
<span class="pln">
</span>
-
<span class="tag">
-
<frame</span>
<span class="pln">
</span>
-
<span class="atn">src
</span>
-
<span class="pun">=
</span>
-
<span class="atv">"..."
</span>
-
<span class="tag">>
</span>
-
</li>
-
<li class="L5">
-
<span class="tag">
-
</frameset>
</span>
-
<span class="pln">
</span>
-
</li>
-
<li class="L6">
-
<span class="tag">
-
</html>
-
</span>
-
</li>
-
</ol>
上图中,1.html和2.html被视为异源。
特立独行的Internet Explorer
在IE中,有两种情况同源策略无效:
TrustZones(信任域):当一个URI被加入到了IE的信任网站区域中时,浏览器会无视同源策略。
IE在考虑同源策略时不包括端口, 这意味着不同端口上的应用程序可以读取到比如用户的登陆账户密码/cookie等。
通过变更自身的源绕过同源策略
IE 6,7版中网页可以通过document.domain设置自身的来源为任意其他来源。如今网页仍然可���更改源,但是有一些限制。
网页可以变更自身的源为父级域名。
例如http://malicious.eth.space/1.html可以通过执行
-
<ol
class=
"linenums">
-
<li class="L0">
-
<span class="pln">document
</span>
-
<span class="pun">.
</span>
-
<span class="pln">domain
</span>
-
<span class="pun">=
</span>
-
<span class="pln">
</span>
-
<span class="str">"eth.space"
</span>
-
<span class="pun">;
</span>
-
</li>
-
<
/ol>
来绕过同源策略的限制,从而可以读取http://eth.space/login.html上的内容。这其中的应用大家可以自己去想。
注意端口不同的情况。
需要注意的是,在改变源时,端口号是需要特别指定的。由于运行以下js代码
-
<ol
class=
"linenums">
-
<li class="L0">
-
<span class="pln">document
</span>
-
<span class="pun">.
</span>
-
<span class="pln">domain
</span>
-
<span class="pun">=
</span>
-
<span class="pln"> document
</span>
-
<span class="pun">.
</span>
-
<span class="pln">domain
</span>
-
</li>
-
<
/ol>
将会导致端口号被重置为null。���以源http://eth.space:1337不能通过修改document.domain="eth.space"来访问http://eth.space的数据,除非后者也设置了document.domain="eth.space",这样双方的源端口号才能一致(null)。反之亦然。
0x05 谈谈防御
如何安全地允许跨源访问?
- 最好的方式是使用CORS,跨源资源共享机制。这个需要的篇幅挺大,我们有机会下次讲。
- 使用Window.postMessage
- 使用JSONP。
如何禁止(你的资源被)跨源访问?
- 为了禁止跨域写,我们需要引入CSRF令牌,然而我们需要正确的配置同源策略,否则CSRF令牌本身也将被恶意网页读取。
- 为了禁止跨域读,我们可以通过设置X-Frame-Options头来禁止该页面被嵌入到恶意页面中,就如同在“经久不衰的BUG”中一样。
- 为了禁止跨域嵌入,确保你的资源本身无法嵌入到各种跨域访问方式中,比如<script src=></script> <img src=x></img>,<svg οnlοad=>,各种字体加载等等。同时,使用CSRF令牌也可以有效避免被跨域嵌入。
如何禁止你控制的网页中嵌入的iFrame做出恶意行为?
使用HTML5的沙盒iframe。
先写到这里吧,事实上,WEB安全中还有几个重要的机制,例如和同源策略相辅相成的跨域资源共享机制(CORS),和内容安全策略(Content Security Policy, CSP)下次再写。