Hu3sky's blog

跨域相关问题

Word count: 1,970 / Reading time: 9 min
2019/04/01 Share

同源策略

同源策略是一种约定,它是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能会收到影响,可以说Web是构建在同源策略上的,浏览器只是针对同源策略的一种实现

来自不同源的对象无法相互干扰
比如a.com带着cookie访问b.com,而b.com能读取到a.com的cookie,所以为了避免这种情况产生,就有了同源策略
使用jQuery向不同源目标站发送请求

1
$.post("http://hu3sky.ooo")

1

这里的Access-Control-Allow-Origin头表示服务端允许哪些源的请求

以下情况被认为是不同源的

1
2
3
4
5
6
7
8
9
10
11
不同协议:
http://hu3sky.ooo
https://hu3sky.ooo

不同端口
http://hu3sky.ooo:80
http://hu3sky.ooo:81

不同域
http://a.hu3sky.ooo
http://b.hu3sky.ooo

只有当域名,端口,协议都相同,才会被认为是同源

1
2
http://hu3sky.ooo/a.php
http://hu3sky.ooo/b.php

而在IE中,未将端口号划分进来,也即是说http://hu3sky.ooo:81/index.htmlhttp://hu3sky.ooo/index.html 属于同源并且不受任何限制。

同源策略的限制

在非同源的情况下,会受到限制
限制之一就是不能通过ajax的方法去请求不同源中的文档,比如XMLHttpRequest方法,只能请求同源对象的内容。 它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的

跨域标签

<img>,<link>,<iframe>,<script>等利用src,href属性都可以跨域加载资源,不受同源策略的限制

跨域资源共享的方式

document.domain

适用范围:

  • 两个域的子域不同
  • 只适用于iframe不同窗口之间互相获取cookie和DOM节点(
    如果文档包含框架(frame 或 iframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象)

当两个不同的域只是子域不同时,可以通过把document.domain设置为他们共同的父域来解决

example

a.hu3sky.com/1.html

1
<iframe src=" http://b.hu3sky.com/2.html" id="iframe">

b.hu3sky.com/2.html

1
<h1>Hello!</h1>

在a.hu3sky.com的窗口下获取iframe里的document对象的name属性,会报错
1

接下来,我们添加document.domain

a.hu3sky.com/1.html

1
2
<script>document.domain = 'hu3sky.com'</script>
<iframe src="http://b.hu3sky.com/2.html" id="iframe" />

b.hu3sky.com/2.html

1
2
<script>document.domain = 'hu3sky.com'</script>
<h1>Hello!</h1>

并未报错
1

注意:

  • 即使你设置了相同的document.domain,也无法通过AJAX去请求b页面的,该方法只适用于iframe不同窗口之间互相获取cookieDOM节点
  • 设置的document.domain域名的级别不能低于当前的域名,比如只能设置为a.hu3sky.comhu3sky.com
  • 在同窗体下,即使不设置document.domain其实也可以获得window对象,不过可用的属性非常少,几乎没用

JSONP

在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的

example

a.hu3sky.com/get_data.html

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<script>
function func(jsondata)
{
alert("json data: "+jsondata); //处理回调的json数据
}
</script>
<script src="http://b.hu3sky.com/json_data.php?callback=func" ></script>
</head>
</html>

b.hu3sky.com/json_data.php

1
2
3
4
5
<?php
header('Content-type: application/javascript');
$callback = $_GET['callback'];
$data = '["hack","the","world"]';
echo $callback . "(" . json_encode($data) . ")";

1

  1. <script>标签用在json_data.php中定义好的callback参数来请求到数据
  2. json_data.php把数据当函数参数传入返回给get_data.html
  3. get_data.html中定义了func函数来处理获取的参数

关于header('Content-type: application/javascript');
先是设置了header的情况
1
并不会造成XSS
没有设置header的时候
1
但是在设置了的情况下,依然可能造成XSS
在application/json,application/javascript等Response下进行XSS

window.name

window对象有个name属性,一个窗口的生命周期内的页面都是共享一个window.name的,每个页面对window.name都有读写权限
即使跳转,也会保留,例如在https://cn.bing.com/的console
1
接着在当前窗口进行跳转
location.href = "www.baidu.com"
1
window.name的值为String

example

a.hu3sky.com/a.html

1
2
3
<script type = "text/javascript">
window.name = "aaaaaaa";
</script>

b.hu3sky.com/b.html

1
<iframe src = "http://a.hu3sky.com/a.html" id = "iframe"></iframe>

c.hu3sky.com/c.html

1
2
3
<script>
alert(window.name);
</script>

1

原理:

  1. a.html设置window.name
  2. b.html利用iframe获取a.html的window对象,前面讲过iframe由于是跨域,不能获取到name属性
  3. 在b.htm通过设置iframel src为c.html,在iframe中的所有页面共享window.name,于是获取到name属性

window.postMessage(HTML5)

window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,该函数的第一个参数为发送的消息,类型只能为字符串;第二个参数targetOrigin用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 *

example

aaa.html

1
2
3
<script>
<iframe src = "http://b.hu3sky.com/bbb.html" id = "iframe">
</script>

bbb.html

1
2
3
4
5
6
7
8
<script>
//onmessage 属性是当对象接收到message 事件时被调用的EventHandler .
window.onmessage = function(e)
{
e = e || event;
alert(e.data);
}
</script>

在控制台发送

1
iframe.contentWindow.postMessage("alert","*")

获取iframe的window对象,调postMessage方法
1
当没对bbb.html进行处理时,可能会造成XSS
aaa.html

1
<iframe src = "http://b.hu3sky.com/bbb.php" id = "iframe"></iframe>

bbb.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
setcookie("password","Adif9gjsk_asda");
?>
<p id="name">aa</p>
<script>
window.onmessage = function(e)
{
e = e || event;
document.getElementById('name').innerHTML = e.data;
}

</script>

控制台发送
iframe.contentWindow.postMessage("<img src=1 onerror=alert(document.cookie)>","*")

1

CORS

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器跨域发送AJAX,例如XMLHttpRequest请求

相比JSONP只能发送get请求,CORS允许发送任何类型的请求。但CORS要求浏览器和服务器同时支持。目前所有浏览器都支持,IE需要IE10以上。

example

aaaa.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
<head>
<script type="text/javascript">
function loadXMLDoc()
{
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open("GET","http://b.hu3sky.com/bbbb.html",true);
xmlhttp.send();
}
</script>
<button type="button" onclick="loadXMLDoc()">请求数据</button>

bbbb.php

1
<h2>AJAX</h2>

当没有设置CORS头时,看到,我们用AJAX去跨域请求数据
1
报错如下

1
已拦截跨源请求:同源策略禁止读取位于 http://b.hu3sky.com/bbbb.html 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')。

当我们在bbbb.php加了CORS头

1
2
3
4
<?php
header("Access-Control-Allow-Origin:http://a.hu3sky.com");
?>
<h2>AJAX</h2>

再次访问,就能够请求到数据了

1

观察请求包和返回包
1

浏览器发现它是简单请求,就会直接在头信息中加一个origin字段
服务器收到这条请求,如果这个origin指定的源在许可范围内,那么服务器返回的头信息中会包含Access-Control-Allow-Origin字段,值与origin的值相同

如果请求需要带上Cookie,则需要服务器设置Access-Control-Allow-Credentials: true否则浏览器将不会把响应内容返回给请求的发送者

注意
当设置Access-Control-Allow-Origin=*时,浏览器处于安全性考虑,即使设置了Access-Control-Allow-Credentials: true,也不向服务端发送cookie

Referers

CATALOG
  1. 1. 同源策略
    1. 1.1. 同源策略的限制
    2. 1.2. 跨域标签
  2. 2. 跨域资源共享的方式
    1. 2.1. document.domain
      1. 2.1.1. example
    2. 2.2. JSONP
      1. 2.2.1. example
      2. 2.2.2. header
    3. 2.3. window.name
      1. 2.3.1. example
    4. 2.4. window.postMessage(HTML5)
      1. 2.4.1. example
    5. 2.5. CORS
      1. 2.5.1. example
  3. 3. Referers