欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Web安全之同源策略与跨域访问

程序员文章站 2024-02-18 16:37:10
...

古语云:“无规矩不成方圆”。同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

一、怎样才算是同源

所谓同源是指域名(主机名或者IP地址)、端口、协议相同。不同的客户端脚本(JavaScriptActionScript)在没明确授权的情况下,不能读取对方的资源。

不同源的例子: 
1. 域名(主机名或者IP地址)不同 
http://news.company.com/index.htmlhttp://www.company.com/index.html不同源,域名不同,news子域与www子域不同。 
http://company.com/index.htmlhttp://www.company.com/index.html 不同源,域名不同,*域与www子域不是一个概念。 
2. 端口不同 
http://www.company.com:8080/index.html与 
http://www.company.com/index.html不同源,端口不同,8080与默认的80端口不同。 
3. 协议不同 
https://www.company.com/index.html与 
http://www.company.com/index.html 不同源,协议不同,httpshttp是不同协议。 
同源的例子: 
http://www.company.com/a/c/index.html与 
http://www.company.com/b/d/index.html 属于同源,域名,端口,协议均相同。

二、IE特例

在处理同源策略的问题上,IE存在两个主要的不同之处。

1. 授权范围(Trust Zones) 
两个相互之间高度互信的域名,如公司域名(corporate domains),不遵守同源策略的限制。 
2. 端口 
IE并没有将端口号加入到同源策略的组成部分之中,因此,https://www.company.com/index.html 与 
http://www.company.com/index.html 属于同源并且不受任何限制。

这些例外都是非标准的,其他也并未作出支持

三、读写权限

Web上的资源有很多,有的只有读权限,有的同时拥有读和写的权限。比如:HTTP请求头里的Referer(表示请求来源)只可读,同源和不同源就是根据这个Referer值进行判断的, 而document.cookie则具备读写权限。这样的区分也是为了安全上的考虑。

注意:Cookie中的同源只关注域名,忽略协议和端口。所以https://localhost:8080/http://localhost:8081/Cookie是共享的。

四、同源策略示例

如果是打开百度,在控制台中请求CSDN的网页的话,会报下面的异常: 
Chrome中会报下面的异常:

Web安全之同源策略与跨域访问

IE中会报下面的异常:

Web安全之同源策略与跨域访问

五、跨域访问资源

1. Ajax跨域(CORS)

Ajax主要是通过XMLHttpRequest对象与远程的服务器进行信息交互的。但是XMLHttpRequest受到同源策略的约束,不能跨域访问资源。 
如果XMLHttpRequest能够跨域访问资源,则会导致安全问题。因为XMLHttpRequest是一个纯粹的JavaScript对象,如果某网站存在漏洞导致XSS注入了JavaScript脚本,这个脚本就可以通过Ajax获取用户的信息并通过Ajax提交到其他站点。 
但是XMLHttpRequest可以通过访问目标域的服务器,然后目标域的服务器返回的HTTP响应头来授权是否允许跨域访问,假如目标站点http://www.foo.com返回的响应头如下: 
Access-Control-Allow-Origin: http://www.evil.com 
那么www.evil.com站点上的客户端脚本就有权通过Ajax技术对www.foo.com上的数据进行读写操作。 
请求及响应过程如下: 
Web安全之同源策略与跨域访问 
通过在HTTP Header中加入扩展字段,服务器在相应网页头部加入字段表示允许访问的domainHTTP method,客户端检查自己的域是否在允许列表中,决定是否处理响应。 
实现的基础是JavaScript不能够操作HTTP Header。某些浏览器插件实际上是具有这个能力的。 
服务器端在HTTP的响应头中加入(页面层次的控制模式):

Access-Control-Allow-Origin: evil.com
Access-Control-Request-Method: GET, POST
Access-Control-Allow-Headers: Content-Type, Authorization, Accept, Range, Origin
Access-Control-Expose-Headers: Content-Range
Access-Control-Max-Age: 3600
  • 1
  • 2
  • 3
  • 4
  • 5

多个域名之间用逗号分隔,表示对所示域名提供跨域访问权限。”*”表示允许所有域名的跨域访问。

客户端可以有两种行为: 
1. 发送OPTIONS请求,请求Access-Control信息。如果自己的域名在允许的访问列表中,则发送真正的请求,否则放弃请求发送。 
2. 直接发送请求,然后检查responseAccess-Control信息,如果自己的域名在允许的访问列表中,则读取response body,否则放弃。

本质上服务端的response内容已经到达本地,JavaScript决定是否要去读取。

Support: [Javascript Web Applications] 
* IE >= 8 (需要安装caveat) 
* Firefox >= 3 
* Safari 完全支持 
* Chrome 完全支持 
* Opera 不支持

CORS协议提升了Ajax的跨域能力,但也增加了风险。一旦网站被注入脚本或XSS攻击,将非常方便的获取用户信息并悄悄传递出去。

2. Jsonp实现跨域访问请求(单向跨域)

JSONP(JSON with Padding)是一个简单高效的跨域方式,HTML中的script标签可以加载并执行其他域的JavaScript,于是我们可以通过script标记来动态加载其他域的资源。例如我要从域A的页面pageA加载域B的数据,那么在域B的页面pageB中我以JavaScript的形式声明pageA需要的数据,然后在pageA中用script标签把pageB加载进来,那么pageB中的脚本就会得以执行。JSONP在此基础上加入了回调函数,pageB加载完之后会执行pageA中定义的函数,所需要的数据会以参数的形式传递给该函数。

第一个站点的测试页面(http://localhost:8080/test.html):

<script src="http://localhost:8081/test_data.js" type="text/javascript"></script> 
<script>
   function test_handler(data) { 
        console.log(data);  
   }  
</script> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

服务器端的Javascript脚(http://localhost:8081/test_data.js):

test_handler('{"data": "something"}');
  • 1

为了动态实现JSONP请求,可以使用Javascript动态插入<script>标签:

<script type="text/javascript">
   // this shows dynamic script insertion
   var script = document.createElement('script');
   script.setAttribute('src', url);
   // load the script
   document.getElementsByTagName('head')[0].appendChild(script); 
</script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

JSONP协议封装了上述步骤,jQuery中统一实现在Ajax中(其中data typeJSONP): 
http://localhost:8080/test?callback=test_handler 
为了支持JSONP协议,服务器端必须提供特别的支持,另外JSONP只支持GET请求。 
利用jQuery中的Ajax实现jsonp跨域请求可以查看我之前的博客 
JavaScript实现百度搜索suggestion功能

3. document.domain(双向跨域)

通过修改documentdomain属性,我们可以在域和子域或者不同的子域之间通信。同域策略认为域和子域隶属于不同的域,比如www.a.comsub.a.com是不同的域,这时,我们无法在www.a.com下的页面中调用sub.a.com中定义的JavaScript方法。但是当我们把它们documentdomain属性都修改为a.com,浏览器就会认为它们处于同一个域下,那么我们就可以互相调用对方的method来通信了。

注意:浏览器单独保存端口号。任何的赋值操作,包括document.domain=document.domain都会以null值覆盖掉原来的端口号。因此company.com:8080页面的脚本不能仅通过设置document.domain="company.com"就能与company.com通信。赋值时必须带上端口号,以确保端口号不会为null。另外,使用document.domain来安全是让子域访问其父域,需要同时将子域和父域的document.domain设置为相同的值。必须要这么做,即使是简单的将父域设置为其原来的值。没有这么做的话可能导致授权错误。

4. window.postMessage(双向跨域)

window.postMessageHTML5定义的一个很新的方法,这个方法可以很方便地跨window通信。由于它是一个很新的方法,所以在很旧和比较旧的浏览器中都无法使用。

例如:

targetWindow.postMessage(data, origin);
  • 1

1.data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。 
2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为”*”,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为”/”。 
接收消息:

window.addEventListener('message', handler, false);
  • 1

handlerevent.datapostMessage发送来的数据,event.origin是发送窗口的originevent.source是发送消息的窗口引用

5. 跨域内嵌的资源

a.<script src="..."></script>标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到 
b.<link rel="stylesheet" href="...">标签嵌入CSS。 
c.<img>嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,… 
d.<video> 和 <audio>嵌入多媒体资源。 
e.<object><embed> 和 <applet>的插件。 
f.@font-face引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。 
g.<frame><iframe>载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

六、参考

1. 百度百科:同源策略 
2. JavaScript 的同源策略 
3. 浏览器的同源策略 
4. 跨域资源共享的10种方式 
5. 同源策略和跨域访问