浏览器同源策略研究

什么是同源策略?

同源策略限制了一个源(origin)上加载的资源或脚本与其他源上的资源进行交互的行为.

这样的跨域交互主要分为三类:

  1. cross-origin writes: 包括(均为指向其他源的)超链接, 重定向以及表单请求(见下文说明)等. 这一类请求基本允许.

  2. cross-origin embedding: 包括<script>, css link, <img>, <video>, <audio>, <frame>/<iframe>等. 这一类请求基本允许.

  3. corss-origin reads: 包括使用ajax获取其他源的数据, 以及使用js读取其他iframe上的DOM信息等. 这一类请求基本不允许.

这里需要着重说明的是所谓不允许是什么意思. 这的不允许是指, 跨域请求无论如何都可以正常发送, 但是你将拿不到这个请求的返回值. 这样一来, 在跨域的前提下普通的表单请求依然可以正常工作(因为本来也拿不到返回值, 也不需要返回值). 而对于Ajax请求来说, 请求虽然可以正常发送到服务器, 但是浏览器会阻止js代码将拿到服务器的返回值.

看起来同源策略很严密了? 其实不然. 现行的同源策略有几个弱点, 举例如下:

  • CSRF攻击, 即恶意站点伪造指向其他被害网站的表单并提交, 从而修改用户在被害网站上的数据

  • XSS攻击. 攻击者在被害网站中植入js脚本(比如网站没有做js或html标签过滤), 获取被害网站中的敏感数据(如cookies)后通过http请求发送到自己的服务器上(比如使用<a>, <img>或者构造表单发送). 只要能发送出去服务器上就会有记录, 从而窃取机密数据.

  • 跨域信息泄露. 简单的技术比如跨域加载的img虽然不能读取内容, 但是可以获取其宽高的数据. 复杂的技术甚至可以探知用户有没有登录某些网站. (这种攻击方式参见https://www.grepular.com/Abusing_HTTP_Status_Codes_to_Expose_Private_Information)

CORS

在默认情况下, 同源策略将按照上述的三个规则进行工作. 但是有时候我们想对同源策略进行调整, 人为地定制(主要是放宽)一些同源策略限制来满足我们的开发需求(俗称"绕过"同源策略). 其中最常用的方法就是使用CORS标准.

前面说到, 即使违反了同源策略, 跨域请求依然是可以发出去的, 只是收不到响应而已(对于Ajax请求来说就是, 响应被浏览器丢弃, 不交给对应的js回调函数). 收不到响应的原因根本上说是存在同源策略, 而直接原因就是浏览器执行了CORS检查.

如上图所示, 服务器在返回响应时, 会加上一个Access-Control-Allow-Origin的Header, 用来告诉用户浏览器这个响应允许被哪些domain上的js读取. 上图的例子中, *表示任意domain都可以读取. 当浏览器发现当前domian不在这个列表中的话, 就会执行响应拒绝. 所以, 如果要允许跨域请求, 只需要调整该字段的值即可. 注意, 如果不指定这个字段, 那么将会执行默认的同源策略限制.

另外还注意到, 发出HTTP请求时还会带着Origin的Header, 这个Header就是发出请求时的domain. 这个字段的一个用法是可以用来对抗CSRF. 比如可以在服务器端对用户请求进行检查, 如果发现Origin不符合就直接拒绝.

上述的流程只限于简单的HTTP请求, 即同时满足:

  1. GET, HEAD, POST 三个方法之一

  2. 只允许修改Accept, Accept-Language, Content-Language以及Content-Type三个报文头(其他必要报文头由浏览器自行填充, 但用户端js不能修改)

  3. Content-Type为application/x-www-form-urlencoded, multipart/form-data, multipart/form-data三者之一

如果有任意一点不满足, 则会先发送一个preflight请求向服务器询问是否允许发送, 得到允许后再发出实际请求.

这里值得重点强调的是preflight存在的意义. preflight本身其实和同源策略或者CORS一点关系也没有, 其主要功能就只是确认服务器是否支持某些请求或操作. 从而避免出现服务器不支持的情况 (比如上古服务器软件或者没有开启某些HTTP操作的服务器), 因此属于兼容性测试的一种. 如果发现服务器不支持, 那么就可以提前终止后续请求的发送.

其他绕过同源策略的技术

具体的技术细节参见: http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html

当两个网页的一级域名相同时, 可以通过修改codument.domain来实现cookies的共享.

而对于两个毫无关系的网页来说, 有以下方式可以实现同源策略的规避:

1. fragment identifier

父页面可以修改子页面(iframe)url中#后的部分而不会引起子页面刷新. 这样就能向子页面传递数据

2. window.name

父页面打开子窗口(window)的情况下, 子窗口的window.name字段不会随子窗口页面的跳转而变化, 因此可以用于在父子窗口中进行信息交换. 还可以对其进行监听.

3. window.postMessage

不同于前两个hack出来的技术. window.postMessage是为了解决跨域通信问题而设计的HTML5 API.

详细信息可参见: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

4. JSONP

JSONP可以从非同源的网站上获取json数据.

一般而言, 要获取一个json数据最常用的方法就是构造一个Ajax请求. 但是这样会被同源策略拦截.

另一个略显奇怪的方法是, 使用<script>标签加载一个在另一个源上的js文件(这属于cross-origin embedding, 是允许的), 而在这个文件中声明了一个全局变量, 值就是你想返回的json数据. 加载后请求端的js就可以读取这个全局变量来获得想要的json数据. 这是完全可行的也实现了跨域数据获取的问题, 但是这样不太方便, 于是就有了JSONP技术.

简单来说就是使用类似于


<scirpt src="http://example.com/ip?callback=foo\"></script>

的写法. 当加载完成后浏览器会自动调用foo函数并传入解析后的json作为第一个参数.

这种方法的局限在于只支持GET操作并只能返回JSON数据.

posted on 2016-12-27 16:03  luMinO  阅读(947)  评论(1编辑  收藏  举报

导航