Introduction

Intro

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互.这是一个用于隔离潜在恶意文件的重要安全机制.

同源策略是一种安全性和可用性的一种平衡, 在遵循”同源”的基础上, 实现”可控”的跨域操作.

源的定义

文档的来源包含协议, 主机, 载入文档的 URL, 端口, 浏览器根据这四者来判断两个不同的
域是否同源. 示例如下:

1
2
3
4
5
6
7
// 同http://store.company.com/dir/page.html同源的例子

http://store.company.com/dir2/other.html // 同源
http://store.company.com/dir/inner/another.html // 同源
https://store.company.com/secure.html // 不同源, 不同协议(https和http)
http://store.company.com:81/dir/etc.html // 不同源, 不同端口(81和80)
http://news.company.com/dir/other.html // 不同源 不同域名(news和store )

其中, IE 在同源策略方面有两点特殊:

  • 授信范围: 两个高度互信的域名(公司域名等), 不遵守同源策略
  • 端口: IE 未将端口放入源的定义中

何为来源

脚本本身的来源和同源策略并不相关, 相关的是脚本所嵌入的文档的来源, 见如下的例子:

加入来自主机 A 的脚本 script-1 被嵌入到主机 B 的某一个 WEB 页面/myname.html 上:

1
2
3
<body>
<script src="http://xx.a.com/script-1.html"></script>
</body>

则用户浏览 B 网站的页面时, 该脚本的源: 脚本所嵌入的文档来源- B 域名, 所以该脚本拥有
权限控制 B 网站的一切权限.

跨域交互分类

交互

同源策略的交互主要分为下面三类:

  • 跨域写(Cross-origin writes), 允许,例如表单提交, 重定向, 链接
  • 跨域嵌入(Cross-origin embedding), 允许, 见 2.2 节内容
  • 跨域度(Cross-origin reads), 一般不允许跨域读

Embedding

下面是可能嵌入跨域的资源示例:

  • script, 嵌入跨域脚本
  • link, 嵌入 CSS
  • img, 嵌入图片
  • video, audio, 嵌入多媒体资源
  • object, embed, applet 插件
  • font-face 引入字体
  • frame, iframe 载入的任何资源

CORS 安全首部

  • ACCEPT
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Width
  • Viewport-Width

跨源访问

源更改

对于拥有同一个”超级域”的所有子域, 可以使用 document.domain 的值来设置当前域以及
当前域的超级域, 之后就能绕过同源策略, 例如:

1
2
3
4
5
6
// 1 对于http://store.company.com/dir/other.html
document.domain = 'company.com';

// 之后, 页面就能通过http://company.com/dir/page.html的同源检测

// 2 同理, 拥有相同超级域的子域都可以这样操作

但是, 这里需要在来源和目的都进行同样的设置, 应该执行上述的操作之后, 会将端口号
置为 null.

跨源网络访问

允许跨源访问: 设置cors策略.

阻止跨源访问:

  • 阻止跨域写操作, 检测请求中的 CSRF TOKEN, 阻止页面的跨站读操作.
  • 阻止跨域读操作, 保证资源不可嵌入, 从而避免暴露嵌入信息
  • 阻止跨站嵌入, 确保嵌入的资源不是embedding的类型.

跨源脚本 API

在 javascript 的 APIs 中, 如 iframe.contentWindow, window.parent, window.open,
window.opener 允许文档间相互引用.

在不同源中的进一步交流, 见 window.postMessage 方法

跨源存储访问

  • 默认情况下, localStorage 和 IndexedDB 以源分隔, 禁止其他源对该内容进行读写操作
  • cookies 则不同, 一个页面可以为本域和任何父域设置 cookie, 浏览器都允许给定的域以及其任何子域名(sub-domains)访问 cookie

CORS

Intro

参考: cors

同源策略在 Client, Server 两端都有检查, 即使在 Server 通过了同源检查(放开权限),
返回的请求也会被 Browser 截断并丢弃.

简单请求

某些请求不会触发 CORS 检查策略, 这些请求术语简单请求, 满足所有条件:

  1. 使用 GET/HEAD/POST 方法之一
  2. 使用集合CORS 安全首部
  3. 其中 content-type 限于: text/plain, multipart/form-data, application/x-www-form-urlencode

例子, 默认服务器放开了同源限制, 例如站点 a.com 欲访问 b.com 的资源:

1
2
3
4
5
6
7
8
9
10
var invocation = new XMLHttpRequest();
var url = 'http://b.com/resources/public-data/';

function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true);
invocation.onreadystatechange = handler;
invocation.send();
}
}

此时的请求和响应报文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://a.com/examples/access-control/simpleXSInvocation.html
Origin: http://a.com


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

注意, 这里的返回值 Access-Control-Allow-Origin, 表示服务器没有任何同源限制

预检请求

对于非简单请求, 需要先使用 OPTIONS 方法发起一个”预检请求”到服务器, 以获取服务器
是否允许该实际请求.

“预检请求”的使用可以避免跨域请求对服务器用户数据产生”非预期”的影响.

下述任何一个符合的请求都属于”预检请求”:

  • 使用 PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH 方法
  • 人为设置了 CORS 安全的首部集合之外的首部字段, 见CORS
  • Content-Type 不属于: application/x-www-form-urlencoded, multipart/form-data, text/plain

HTTP 响应

  • Access-Control-Allow-Origin: 指定了允许访问该资源的外域 URI
  • Access-Control-Expose-Headers: 指定允许浏览器访问的头
  • Access-Control-Max-Age: 指定了 preflight 请求的结果能够被缓存多久
  • Access-Control-Allow-Credentials: 指定了当浏览器的 credentials 设置为 true 时是否允许浏览器读取 response 的内容
  • Access-Control-Allow-Methods: 指明了实际请求所允许使用的 HTTP 方法
  • Access-Control-Allow-Headers: 指明了实际请求中允许携带的首部字段

HTTP 头部

该字段无需手动设置

  • Origin: 首部字段表明预检请求或实际请求的源站
  • Access-Control-Request-Method: 实际请求所使用的 HTTP 方法告诉服务器
  • Access-Control-Request-Headers: 实际请求所携带的首部字段告诉服务器

比如,发送 POST 请求之前的一个 OPTIONS 请求头如下:

1
2
3
4
5
6
7
--header 'Access-Control-Request-Headers: authorization,content-type' \
--header 'Access-Control-Request-Method: POST' \
--header 'Cache-Control: no-cache' \
--header 'Connection: keep-alive' \
--header 'Origin: http://127.0.0.1:8082' \
--header 'Pragma: no-cache' \
--header 'Referer: http://127.0.0.1:8082/' \

对应的响应头如下:

1
2
3
4
access-control-allow-credentials  true
access-control-allow-origin http://127.0.0.1:8082
access-control-allow-headers accept, authorization, content-type, user-agent
access-control-allow-methods DELETE, GET, OPTIONS, PATCH, POST, PUT