同源策略与跨域请求

web安全 2019-11-10

本文作者:晚风(来自信安之路作者团队)

做前端开发经常会碰到各种跨域问题,通常情况下,前端除了 iframe 、script 、link、img、svg 等有限的标签可以支持跨域外(这也与这些标签的用途有关),其他的资源都是禁止跨域引用的。说到跨域,与浏览器的同源策略是密不可分的。那我们先来理解一下浏览器为什么要设置同源策略。

同源策略(same origin policy)

首先,同“源”的源不单单是指两个页面的主域名,还包括这两个域名的协议、端口号和子级域名相同。举个例子,假设我现在有一个页面http://www.a.com/index.html,域名是 www.a.com,二级域名为 www,协议是 http,端口号是默认的 80,这个页面的同源情况如下:

img

同源策略存在的意义就是为了保护用户的信息的安全。一般网站都会把关于用户的一些敏感信息存在浏览器的 cookie 当中试想一下,如果没有同源策略的保护,那么 b 页面也可以随意读取 a 页面存储在用户浏览器 cookie 中的敏感信息,就会造成信息泄露。如果用户的登录状态被恶意网站能够随意读取,那后果不堪设想。由此可见,同源策略是非常必要的,可以说是浏览器安全的基石。

除了 cookie 的访问受到同源策略的限制外,还有一些操作也同样受到同源策略的限制:

(1) 无法读取非同源网页的 Cookie 、sessionStorage 、localStorage 、IndexedDB

(2) 无法读写非同源网页的 DOM

(3) 无法向非同源地址发送 AJAX请求(可以发送,但浏览器会拒绝响应而报错)

虽然所有的页面都有浏览器的同源策略的保护,但我们仍然有一些办法绕过浏览器的同源策略限制。下面我总结一下目前已知的几种跨域方法和可能会导致的安全问题。如果有什么其他的方法,欢迎在下面留言补充。

五种前端跨域方法

1、JSONP

JSONP(json with padding) ,利用了 `` 标签能跨域的特性。需要前端和后端约定好一个函数名,当前端请求后端时,返回一段 JS。这段 JS 调用了之前约定好的回调函数,并将数据当作参数传入,完成数据的跨域传递。就这样看文字可能有点难以理解,我们来看一个例子:

需要获取数据的页面 http://a.com/index.html:

 <script>    function foo(data) {        console.log(data.txt);    }</script><script src="http://b.com/api?callback=foo"></script>

访问非同域的页面 http://b.com/api?callback=foo,返回数据如下:

 foo({ txt : 'example' })

在这个例子中,约定好的回调函数名为 foo,由 callback 参数告知服务器要调用哪个函数来传递数据,也就是要传递什么数据。然后服务器就会返回相应的数据给页面,从而实现了数据的跨域传输。这就是整个 JSONP 的流程。由于这种方式是利用了 script 标签的特性来实现跨域,所以只能用 GET 方式获取数据。

这种方式有几个安全问题:

一是接收请求的 api 页面是属于公共使用的那还好,如果是内部私用就会造成一个用户信息泄露的问题;

二是如果 callback 参数的内容是一段 js 代码,当后端没有过滤或者过滤不严时,可能会出现 XSS 问题;三是有可能会出现 SOME 漏洞。

2、document.domain

这种方式有些局限性,只能在顶级域名相同的两个页面中跨域访问。通常情况下,在一个页面中内嵌一个不同域的 iframe,两个页面是无法相互访问的,所以多用于控制页面中内嵌的 iframe。然后再来说下 js 中的 document.domain 这个东西。这个东西会显示当前页面的域名。也可以设置,但有限制,只能设置成当前的域名或者顶级域名。如果设置为其他的域名则会报一个“参数无效”的错误:

 document.domain                //www.a.comdocument.domain = 'www.a.com'  //www.a.comdocument.domain = 'a.com'      //a.comdocument.domain = 'b.com'      //参数错误

举个例子,现在有两个页面,http://www.a.comhttp://a.com,这两个页面是不同域的。前一个域名的 document.domain 是 www.a.com ,后一个域名的 document.domain 是 a.com 。在前一个页面中引入后一个页面,并把前一个页面的 document.domain 改为 a.com ,那么这两个页面就能相互操作了,也就是实现了同一顶级域名之间的跨域。

3、window.name

关于 window.name 我们先来看这样一个场景,首先是 A 站:

 window.name = 'A site data';location.href = 'http://b.com';

设置完 window.name 后自动跳转到 B 站,此时我们在控制台里 alert 出 window.name ,发现还是我们在 A 站中设置的数据。可以看到如果在一个标签里跳转页面的话,我们的 window.name 是不会改变的。我们可以了利用这个特性进行跨域。顺便提一下,从页面内部打开的另一个页面会包含前一个页面的引用,这也是 target="_blank" 漏洞的成因。

相同的技术也可以用在 iframe 的跨域数据传递中。

这是含有数据的页面:

img

这是取数据的页面:

img

然后浏览器不给情面地给了一个报错...

img

我不就是跨了个域嘛,就给我一个大大的报错。没事,我们有 JS 黑魔法。对代码进行少许改动,在 iframe 加载完后立即把它的 src 改为同域,这样就可以读取 iframe 的 window.name 了。在实际开发中也可以不设置为同域,而设置为 about:blank,因为这个页面中包含了同域的引用,而且因为是空白页面,可以提高页面加载速度。

img

成功跨域拿到了数据:

img

在安全中,我们一般使用 window.name 来缩短 XSS 的 payload。

4、postMessage

postMessage 是 HTML5 时代新出现的 API。用于安全的进行跨域请求。从字面意思就可以想象,就是一个页面对另一个页面发消息来实现跨域通信。下面是最简单的一个例子:

A 站是发送数据端:

 <iframe src="http://b.com" id="com_b"></iframe>...<script>    document.getElementById('com_b').contentWindow.postMessage('hello b','*')</script>

B 站是接收数据端:

 <script>    window.addEventListener('message',function(event){        alert(event.data);    },false);</script>

以上就是 postMessage 的最简单的一个 Demo。这套跨域机制本身是没有问题的,安全问题都出在人身上。

主要有两个地方容易出问题:

一是 A 站作为消息发送端,如果对消息的保密性有要求,那就不能把接收端写为 *,需要指定接收消息的域;

二是 B 作为数据接收端,需要在处理接收到的数据之前对数据的来源做验证,防止来源被恶意伪造,从而页面被注入恶意数据。所以推荐使用以下的做法:

 <script>    window.addEventListener('message',function(event){        var origin = event.origin || event.originalEvent.origin;  //兼容处理        if(origin !== 'http://a.com') return;        handle(event.data);    },false);</script>

5、browser SOP bug

虽然所有的浏览器都有同源策略,但是各家浏览器实现的方式也是各不相同。难免实现也会有漏洞。我们可以找出浏览器同源策略的漏洞来实现跨域访问。例如浏览器对 CSS(层叠样式表)的松散解析就会导致跨域bug的出现。可以看这个例子:

https://bugs.chromium.org/p/chromium/issues/detail?id=9877

三种服务器跨域方法

1、反代服务器

由于服务器是可以跨域访问数据的。于是我们前端想要什么别的域的数据直接告诉后端服务器,让服务器帮我们去跨域读数据,获取到了再传给我们,这样前端也可以处理别的域内的数据了。也就是说,将其他域名的资源映射到你自己的域名之下,这样浏览器就认为他们是同源的。这就是反代服务器的原理,是不是非常简单。一般我们常常使用 nginx 来配置反向代理服务器。

2、CORS

CORS 设置起来非常简单。只要在相关页面返回一个 “Allow-Control-Allow-Origin” 的响应头即可。类似:

 header("Allow-Control-Allow-Origin: http://www.a.com")

具体可以看阮一峰老师的这篇文章:

http://www.ruanyifeng.com/blog/2016/04/cors.html

CORS 跨域这种方式更像是 JSONP 的升级版,像是给 JSONP 加了一个白名单。只有服务器白名单中的请求才能被正确的响应。

在本届 DEFCON 大会上也提到了这种跨域方式的不安全性。其实大多数问题还是出在没有正确配置 ACAO 响应头上,如果直接设为 *,这就相当于直接把浏览器的同源策略去掉了,所有的域都能访问这个域的文件了。这边提供给了一个 CORS 跨域扫描器,用来检测网站的 CORS 配置是否安全。

https://github.com/chenjj/CORScanner

3、flash

对 flash 跨域的了解不多。这项技术在网页方面也在没落。这里就简单地提一下。假设 A 站要跨域访问B站,首先会检查 B 站下的 crossdomain.xml 文件,如果没有,则访问不成功。如果有,且里面设置了允许 A 站点访问,那么 AB 站点就可以跨域通信了。下面是一个 crossdomain.xml 文件的例子:

 <?xml version="1.0"?><cross-domain-policy>  <allow-access-from domain="*" /></cross-domain-policy>

如果不想域内的文件被其他任何域都能访问到,那么这种做法是不推荐的。正确的做法应该是明确指定本域内的文件能被哪些域访问。


本文由 信安之路 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论