同源策略
John_Frod Lv4

同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

源的定义

如果两个URL拥有相同的协议(http、https)、端口和主机,那么这两个URL就是同源的。

example:

URL 是否同源 原因
http://store.company.com/dir/page.html base
http://store.company.com/dir2/other.html 同源 只有路径不同
http://store.company.com/dir/inner/another.html 同源 只有路径不同
https://store.company.com/secure.html 不同源 协议不同
http://store.company.com:81/dir/etc.html 不同源 端口不同
http://news.company.com/dir/other.html 不同源 主机不同

对于about:blankjavascript:这种特殊的 URL,他们的源应当是继承自加载他们的页面的源,他们本身并没有『源』的概念。

目的

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的 Cookie,会发生什么?

很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

由此可见,”同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。

限制范围

同源策略限制非同源的三种行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取。

  • DOM 无法获得。

  • AJAX 请求不能发送。

跨源访问

我们已经知道了,浏览器会根据同源策略允许或拒绝加载某些资源,但是又一个问题由此而生,我们的网站通常会将静态文件(CSS,JS, 图片)等放置在 CDN 上,那么 CDN 与当前域必然是不同源的,但是神奇的是,这些网站可以正常加载出他们需要的资源并展示给用户,这里为什么又不受同源策略的影响呢?

同源策略控制不同源之间的交互,例如在使用XMLHttpRequest<img>标签时则会受到同源策略的约束。这些交互通常分为三类:

  • 跨域写操作(Cross-origin writes)一般是被允许的。例如链接(links),重定向以及表单提交。

  • 跨域资源嵌入(Cross-origin embedding)一般是被允许。

  • 跨域读操作(Cross-origin reads)一般是不被允许的,但常可以通过内嵌资源来巧妙的进行读取访问。

以下是可能嵌入跨源的资源的一些示例:

  • <script src="..."></script> 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。
  • <link rel="stylesheet" href="..."> 标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的Content-Type 消息头。
  • <img> / <video> / <audio> 嵌入多媒体资源。
  • <object> <embed><applet> 的插件。
  • @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  • <frame><iframe> 载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

1、具备src的标签

  • 原理:所有具有src属性的HTML标签都是可以跨域的

在浏览器中,<script><img><iframe><link>这几个标签是可以加载跨域(非同源)的资源的,并且加载的方式其实相当于一次普通的GET请求,唯一不同的是,为了安全起见,浏览器不允许这种方式下对加载到的资源的读写操作,而只能使用标签本身应当具备的能力(比如脚本执行、样式应用等等)。

2、JSONP跨域

  • 原理:<script>是可以跨域的,而且在跨域脚本中可以直接回调当前脚本的函数

<script>标签是可以加载异域的JavaScript并执行的,通过预先设定好的callback函数来实现和母页面的交互。它有一个大名,叫做JSONP跨域,JSONP是JSON with Padding的略称。它是一个非官方的协议,明明是加载script,为啥和JSON扯上关系呢?原来就是这个callback函数,对它的使用有一个典型的方式,就是通过JSON来传参,即将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。JSONP只支持GET请求。

a网站的前端代码:

1
2
3
4
5
6
<script type="text/javascript">
function dosomething(jsondata){
//处理获得的json数据
}
</script>
<script src="http://haorooms.com/data.php?callback=dosomething"></script>

b网站的后台代码:

1
2
3
4
5
<?php
$callback = $_GET['callback'];//得到回调函数名
$data = array('a','b','c');//要返回的数据
echo $callback.'('.json_encode($data).')';//输出
?>

JSONP也存在一些安全问题,例如当对传入/传回参数没有做校验就直接执行返回的时候,会造成XSS问题。没有做Referer或Token校验就给出数据的时候,可能会造成数据泄露。

3、跨域资源共享(CORS)

  • 原理:服务器设置Access-Control-Allow-Origin HTTP响应头之后,浏览器将会允许跨域请求

CORS是HTML5标准提出的跨域资源共享(Cross Origin Resource Share),支持GET、POST等所有HTTP请求。CORS需要服务器端设置Access-Control-Allow-Origin头,否则浏览器会因为安全策略拦截返回的信息。

1
2
Access-Control-Allow-Origin: *              # 允许所有域名访问,或者
Access-Control-Allow-Origin: http://a.com # 只允许所有域名访问

4、document.domain

  • 原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域

我们只需要在跨域的两个页面中设置document.domain就可以了。修改document.domain的方法只适用于不同子域的框架间的交互,要载入iframe页面。

example:

  1. 在页面 http://a.example.com/a.html 设置document.domain
1
2
3
4
5
6
7
8
<iframe id = "iframe" src="http://b.example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'example.com';//设置成主域
function test(){
alert(document.getElementById('iframe').contentWindow);
//contentWindow 可取得子窗口的 window 对象
}
</script>
  1. 在页面 http://b.example.com/b.html 中设置document.domain
1
2
3
4
<script type="text/javascript">
document.domain = 'example.com';
//在iframe载入这个页面也设置document.domain,使之与主页面的document.domain相同
</script>

5、window.name

  • 原理:window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。

这里有三个页面:

  • sever.com/a.html 数据存放页面
  • agent.com/b.html 数据获取页面
  • agent.com/c.html 空页面,做代理使用

a.html中,设定window.name作为需要传递的值

1
2
3
4
<script>
window.name = 'I was there!';
alert(window.name);
</script>

b.html中,当iframe加载后将iframe的src指向同域的c.html,这样就可以利用iframe.contentWindow.name获取要传递的值了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<script type="text/javascript">
iframe = document.createElement('iframe');
iframe.style.display = 'none';
var state = 0;
iframe.onload = function() {
if(state === 1) {
var data = JSON.parse(iframe.contentWindow.name);
alert(data);
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if(state === 0) {
state = 1;
iframe.contentWindow.location = 'http://agent.com/c.html';
}
};
iframe.src = 'http://sever.com/a.html';
document.body.appendChild(iframe);
</script>
</body>

6、window.postMesage

  • 原理: HTML5新增的postMessage方法,通过postMessage来传递信息,对方可以通过监听message事件来监听信息。可跨主域名及双向跨域。

这里有两个页面:

  1. agent.com/index.html
  2. server.com/remote.html

本地代码index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>  
<iframe id="proxy" src="http://server.com/remote.html" onload = "postMsg()" style="display: none" ></iframe>
<script type="text/javascript">
var obj = {
msg: 'hello world'
}
function postMsg (){
var iframe = document.getElementById('proxy');
var win = iframe.contentWindow;
win.postMessage(obj,'http://server.com');
}
</script>
</body>

postMessage的使用方法: otherWindow.postMessage(message, targetOrigin);

  • otherWindow: 指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口
  • message: 是要发送的消息,类型为 String、Object (IE8、9 不支持)
  • targetOrigin: 是限定消息接收范围,不限制请使用 *

server.com上remote.html,监听message事件,并检查来源是否是要通信的域。

1
2
3
4
5
6
7
8
9
<head>
<title></title>
<script type="text/javascript">
window.onmessage = function(e){
if(e.origin !== 'http://localhost:8088') return;
alert(e.data.msg+" from "+e.origin);
}
</script>
</head>

7、片段标识符

  • 原理:片段标识符(fragment identifier)指的是,URL的#号后面的部分,比如http://example.com/x.html#fragment#fragment。如果只是改变片段标识符,页面不会重新刷新。

父窗口可以把信息,写入子窗口的片段标识符。

1
2
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;

子窗口通过监听hashchange事件得到通知。

1
2
3
4
5
6
window.onhashchange = checkMessage;

function checkMessage() {
var message = window.location.hash;
// ...
}

同样的,子窗口也可以改变父窗口的片段标识符。

1
parent.location.href= target + "#" + hash;

内容安全策略(CSP)

CSP的主要目标是减少和报告XSS攻击. XSS攻击利用浏览器对从服务器接受的内容的信任。恶意的脚本在受害的浏览器被执行, 因为浏览器相信内容源,甚至当内容源并不是从它应该来的地方过来的。

CSP使服务器管理员能够通过制定浏览器能够执行的可信赖脚本的域名来减少或者消除由XSS可能出现的矢量。 一个兼容CSP的浏览器将只会执行加载与白名单域名的源文件的脚本,忽略那些其他的脚本(包括内联脚本和事件操控HTML属性)

具体内容可以看:Content Security Policy 入门教程

参考资料

再谈同源策略

跨域方法总结

浏览器同源政策及其规避方法

  • Post title:同源策略
  • Post author:John_Frod
  • Create time:2021-03-13 20:16:39
  • Post link:https://keep.xpoet.cn/2021/03/13/同源策略/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.