同源策略和跨域请求研究

一、同源策略

假设有一个需求,需要向另外的网站请求数据,例如抓取谷歌搜索的结果。然后写这么一个请求,搜索内容为hello:

或者用原生的更直观:

执行后,浏览器会报错:

12

大意是说localhost域名无法向google.com域名请求数据。

因为同源策略的限制,不同域名、协议(http、https)或者端口无法直接进行ajax请求。 同源策略只针对于浏览器端,浏览器一旦检测到请求的结果的域名不一致后,会堵塞请求结果。这里注意,跨域请求是可以发去的,但是请求响应response被浏览器堵塞了

写了一个程序做验证——用node开了个服务,监听在9000端口,然后在8000端口打开一个页面,再向9000端口的服务发请求:

服务将收到的请求数据打印出来:

818663-20160218205540691-391278757

服务收到了请求,并正常返回数据,但是返回的数据被浏览器干掉了,即使是返回码也无法得到了。所以说同源策略是限制了不同源的读,但不限制不同源的写。那么我们的问题来了,为什么不直接限制写呢,只限制读有什么好处呢?在回答这个问题之前,先要了解同源策略的作用。

假设我打开了A网银http://Abank.com,已经通过了登陆验证,然后再打开了另外一个黑网站http://evil.com,这个网站刚好是抓使用Abank.com的肉鸡。在evil.com的代码里会向Abank.com发请求,例如转账请求,将余额转到自己的账户。但是由于同源策略的限制,使得这种做法无法成功。这个怎么解释呢?

因为evil.com无法获取你在Abank.com的信息,包括验证身份的信息——通常是按照一定规则生成的无法猜到的随机token字符串。token可能放在cookie里面,从evil.com向Abank发请求时,是不会带上Abank的cookie的,同时也不会带上evil.com的cookie,虽然cookie是和域名绑定的。由于没有正确的token值,导致无法通过服务的身份验证。

为验证没带cookie,在上面的例子,localhost向server.com请求数据,服务将收到的cookie打印出来是undefined:

13

然而localhost已经设置了cookie:

13

server.com也有设置cookie:

15

回到上面的问题,为什么不限制写呢?那是因为如果连请求也不出去,那在源头上就限制死了,网站之间就无法共享资源了。另外,限制读即浏览器拦截请求结果,一般情况下就够了,一方面如果访问的是黑网站,那么网站无法跟据请求结果继续下一步的操作,如不断地猜测密码,另一方面如果访问的是白网站,block掉请求结果,应该是考虑到了请求结果可能会使得页面重定向,或者是给网页添加一个恶意的iframe之类的。

有什么办法可以绕过同源策略?有一个办法就是CSRF攻击

二、CSRF攻击

如上面的例子,由于同源策略的限制,跨域的ajax请求不会带cookie,然而script/iframe/img等标签却是支持跨域的,所以在请求的时候是会带上cookie的。还是上面的例子,如果登陆了Abank.com,那么cookie里面就有了tocken,同时又打开了另外一个标签页访问了evil.com,这个网页里面有一个iframe:

这个iframe的src是一个Abank.com的转账的请求,如果Abank.com的转账请求没有第二重加密措施的话,那么请求转账就成功了!

第二个例子是路由器的配置,假设我在网上找到了一个路由器配置教程的网站。这个网站里面偷偷地加一个img标签:

其中192.168.1.1是很多路由器的配置地址。这个1像素的图片没加载出来被忽略了,但是它的请求却发出去了。这个请求给路由器添加了一个vpn代理,指向黑客的代理服务器。如果路由器也是把登陆验证放在cookie里面,那么这个设置vpn的请求很可能就成功了,以后的连接路由器的每个请求都会先经过黑客的服务。

到这里,很明显一个防CSRF攻击的策略就是将token添加到请求的参数里面,也就是说每个需要验证身份的请求都要显式地带上token值。详见:Cross-Site Request Forgery Guide: Learn All About CSRF Attacks and CSRF Protection

 

用script引用的外域的资源一方面可以像上面一样当作一个跨域的请求,另外一方面虽然资源是不可见的,但是script里面定义的全局对象是可用的,如引用jQuery的CDN,定义的一个全局对象jQuery。所以根据这个特性,在某些条件下可以获得到script返回的需要登陆才能得到的数据,有兴趣的可参见:Plain text considered harmful: A cross-domain exploit

跨域攻击可以采取一些措施进行规避,但是跨域更多的还是一些实际的正常应用。

三、跨域请求

有时候在自己的网站需要一些去别人的网站请求数据,这个时候就需要跨域正常请求。方法有很多:

1. 跨域资源共享(CORS)

很多天气、IP地址查询的网站就采用了这样的方法,允许其它网站对其请求数据,例如IP location,可以在自己网站的js里面向它发一个get请求:

它就会返回ip地址信息,同时不会被浏览器拦截:

16

观察response的头部,可以发现添加了一个字段:

17

Access-Control-Allow-Origin就是所谓的资源共享了,它的值*表示允许任意网站向这个接口请求数据,也可以设置成指定的域名,如:

在node.js服务里面添加这个头,那么只有http://yoursite.com能够正常的进行跨域请求。更多地,还可以指定请求的方式、时间等,详见:HTTP访问控制(CORS)

2. JSONP

另外一个常用的办法是使用jsonp,这个方法的原理是客户端告诉服务一个回调函数的名称,服务在返回的scritp里面调用这个回调函数,同时传进客户端需要的数据,这样返回的代码就在浏览器执行了。

例如8000端口要向9000端品请求数据,在8000端口的页面文件定义一个回调函数writeDate,将writeDate写在script的src的参数里,这个script标签向9000端口发出请求:

服务端返回一个脚本,在这个脚本里面执行writeDate函数:

浏览器就执行了这个script片段:

18

这样就实现了跨域的效果。jQuery的ajax里的jsonp的类型,就是用了这样的办法,只是jQuery将它封装好了,使用起来形式跟普通的get/post一样,但是原理是不一样的。

JSONP和CORS相比较,缺点是只支持get类型,无法支持post等其它类型,必须完全信任提供服务的第三方,优点是兼容性较好。

3. 子域跨父域

子域跨父域是支持的,但是需要显式将子域的域名改成父域的,例如mail.mysite.com要请求mysite.com的数据,那么在mail.mysite.com脚本里需要执行:

4. iframe跨父窗口

如果iframe与父窗口也有同源策略的限制,父域无法直接读取不同源的iframe的DOM内容以及监听事件,但是iframe可以调用父窗口提供的api。iframe通过window.parent得到父窗口的window对象,然后父窗口定义一个全局对象供iframe调用。

例如在页面通过iframe的方式嵌入一个youtobe的视频,如果需要手动播放视频、监听iframe的播放事件,页面需要引入youtobe的视频播放控制api,在这个js文件里面定义了一个全局对象YT:

而在视频iframe的脚本里通过window.parent获取得到父窗口即自己网站的页面:

自已网站的页面也是在这个YT对象自定义一些东西,如添加播放事件监听:

5. window.postMessage

在上面第(4)点,父窗口无法向不同源的iframe传递东西,通过window.postMessage可以做到,父窗口向iframe传递一个消息,而iframe监听消息事件。

例如在8000端口的页面嵌入了一个9000端口的iframe:

然后9000端口post一个message:

postMessage执行的上下文必须是接收信息的window,传递两个参数,第一个是数据,第二个是目标窗口。

同时,iframe即9000端口的页面监听message事件:

这样子iframe就可收到父窗口的信息了:

19

同理iframe也可以向父窗口发送消息:

父窗口收到:

20

window.postMessage也适用于通过window.open打开的子窗口,方法类似。

补充一点,如果iframe与父窗口是同源的,则父窗口可以直接获取到iframe的内容,这个方法常用于无刷新上传文件

 

原博客园地址:http://www.cnblogs.com/yincheng/p/cross-domain.html

同源策略和跨域请求研究》有5个想法

  1. “同源政策”三种行为受到限制。
    (1) Cookie、LocalStorage 和 IndexDB 无法读取。
    cookie 能通过一级域名来让二级域名共享
    (2) DOM 无法获得。iframe
    片段识别符(fragment identifier)
    window.name
    跨文档通信API(Cross-document messaging)
    (3) AJAX 请求不能发送。
    JSONP
    WebSocket
    CORS

  2. > 服务收到了请求,并正常返回数据,但是返回的数据被浏览器干掉了,即使是返回码也无法得到了。所以说同源策略是限制了不同源的读,但不限制不同源的写。
    > 那么我们的问题来了,为什么不直接限制写呢,只限制读有什么好处呢?在回答这个问题之前,先要了解同源策略的作用。

    这是博主的一段话,其中一句是:“所以说同源策略是限制了不同源的读,但不限制不同源的写”
    看了文章,我根据自己的理解,我认为博主要表达的是:同源策略限制了对请求数据的读取,但是没有限制请求的发送。
    个人认为表达成 ”读“ 和 ”写“ 有点容易误导人,比如是容易对应到 GET UPDATE请求。

发表评论

电子邮件地址不会被公开。