0x01:CORS简介

CORS和CSP的区别,实质都是跨域,CORS是浏览器允许获取的网站凭证或数据,而CSP是通过浏览器保护你所访问的内容的安全。

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。

允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。
这些跨域请求与浏览器发出的其他跨域请求并无二致。如果服务器未返回正确的响应首部,则请求方不会收到任何数据。

0x02:同源策略简介

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。

可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略在浏览器安全中是一种非常重要的概念,大量的客户端脚本支持同源策略,比如JavaScript。

同源策略允许运行在页面的脚本可以无限制的访问同一个网站(同源)中其他脚本的任何方法和属性。当不同网站页面(非同源)的脚本试图去互相访问的时候,大多数的方法和属性都是被禁止的。

这个机制对于现代web应用是非常重要的,因为他们广泛的依赖http cookie来维护用户权限,服务器端会根据cookie来判断客户端是否合法,是否能发送机密信息。
浏览器要严格隔离两个不同源的网站,目的是保证数据的完整性和机密性。

“同源”的定义:

  • 域名
  • 协议
  • tcp端口号

http://www.atest.cn/test/index.html 的同源检测结果

URL 结果 原因
http://www.atest.cn/aaaa/index.html 成功 域名、协议、端口相同
http://www.atest.cn/bbbb/index.html 成功 域名、协议、端口相同
http://www.atest.com/aaaa/index.html 失败 域名不同
https://www.atest.cn/bbbb/index.html 失败 协议不同
http://www.atest.cn:8080/bbbb/index.html 失败 端口不同

从上表格中可以看出来第一行与第二行都是成功的,这是因为这两个URL比对其他的URL来说只是不同的目录与不同的文件,而同源策略只关心他们是不是同一个域名,同样的协议,同样的端口。.

下面这个图展示的是:如果不启用cors的时候,恶意脚本发出一个请求之后发生的事情image.png

0x03: CORS跨域共享的出现

同源策略对于大型应用有太多的限制,比如有多个子域名的情况
现在已经有大量技术可以放宽同源策略的限制,其中有一种技术就是跨域资源共享(CORS)

CORS是一种机制,这种机制通过在http头部添加字段,通常情况下,web应用A告诉浏览器,自己有权限访问应用B。这就可以用相同的描述来定义“同源”和“跨源”操作。

CORS的标准定义是:通过设置http头部字段,让客户端有资格跨域访问资源。通过服务器的验证和授权之后,浏览器有责任支持这些http头部字段并且确保能够正确的施加限制。
主要的头部字段包含:“Access-Control-Allow-Origin”

  1. Access-Control-Allow-Origin: https://example.com

这个头部字段所列的“源”可以以访客的方式给服务器端发送跨域请求并且可以读取返回的文本,而这种方式是被同源策略所阻止的。

默认情况下,如果没有设置“Access-Control-Allow-Credentials”这个头的话,浏览器发送的请求就不会带有用户的身份数据(cookie或者HTTP身份数据),所以就不会泄露用户隐私信息。
下面这个图展示一个简单的CORS请求流:
image.png

0x04 跨域限制的作用

跨域限制最主要的功能就是为了用户的上网安全。
如果没有浏览器同源策略,那么就很容易发生一些安全事件

例如:
在用户无感知的情况下,获取用户数据

0x05 CORS请求类别

  • 简单请求(simple request)
  • 非简单请求(not-so-simple request)

简单请求分辨方法

只要同时满足以下两大条件,就属于简单请求

一、 请求方法是以下三种方法之一:

  1. HEAD
  2. GET
  3. POST

二、HTTP的头信息不超出以下几种字段:

  1. Accept
  2. Accept-Language
  3. Content-Language
  4. Last-Event-ID
  5. Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain,这个简单的意思就是说,设置了一个白名单,符合这个条件的才是简单请求。其他不符合的都是非简单请求

非简单请求

只要不能同时满足上面的两个条件就通通属于非简单请求

0x06 简单请求与非简单请求浏览器处理结果

相同点

在请求中都需要附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。

例如:Origin: http://www.atest.cn

简单请求

如果它符合上面所述的简单跨域请求,浏览器就会立刻发送这个请求

然后服务器如果认为这个请求可以接受,就会在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,会回发 * )。

例如返回包中会带:Access-Control-Allow-Origin:http://www.atest.cn

那么这时就可以当成一个正常的http请求访问获取数据了

非简单请求

如果浏览器检查之后发现这是一个非简单请求,比如请求头含有X-Forwarded-For字段。

这时候浏览器不会马上发送这个请求,而是有一个preflight,跟服务器验证的过程。

浏览器先发送一个options方法的预检请求。

例如如下请求:

  • Origin:普通的HTTP请求也会带有,在CORS中专门作为Origin信息供后端比对,表明来源域。
  • Access-Control-Request-Method: 接下来请求的方法,例如GET,POST,PUT, DELETE等等
  • Access-Control-Request-Headers: (可选)自定义的头部信息,多个头部以逗号分隔所有用setRequestHeader方法设置的头部都将会以逗号隔开的形式包含在这个头中。

如果服务器配置了cors,会返回对应对的字段

  • Access-Control-Allow-Origin:
  • Access-Control-Allow-Methods:
  • Access-Control-Allow-Headers:

然后浏览器再根据服务器的返回值判断是否发送非简单请求。
简单请求前面讲过是直接发送,只是多加一个origin字段表明跨域请求的来源。
然后服务器处理完请求之后,会再返回结果中加上如下控制字段

  • Access-Control-Allow-Origin: 允许跨域访问的域,可以是一个域的列表,也可以是通配符”*”。这里要注意Origin规则只对域名有效,并不会对子目录有效。即http://a.test.cn/xxxx/ 是无效的。但是不同子域名需要分开设置,这里的规则可以参照同源策略
  • Access-Control-Allow-Credentials: 是否允许请求带有验证信息
  • Access-Control-Expose-Headers: 允许脚本访问的返回头,请求成功后,脚本可以在XMLHttpRequest中访问这些头的信息
  • Access-Control-Max-Age: 缓存此次请求的秒数。在这个时间范围内,所有同类型的请求都将不再发送预检请求而是直接使用此次返回的头作为判断依据,非常有用,大幅优化请求次数
  • Access-Control-Allow-Methods: 允许使用的请求方法,以逗号隔开
  • Access-Control-Allow-Headers: 允许自定义的头部,以逗号隔开,大小写不敏感

重要说明

默认情况不会包含cookie 信息的!!!!!
如果需要包含 cookie 信息
ajax 请求需要设置 xhr 的属性 withCredentials 为 true
服务器需要设置响应头部 Access-Control-Allow-Credentials: true

允许多个源

协议建议,可以简单的利用空格来分隔多个源,比如:

  1. Access-Control-Allow-Origin: https://example1.com https://example2.com

然而,没有浏览器支持这样的语法
通常利用通配符去信任所有的子域名也是不行的,比如:

  1. Access-Control-Allow-Origin: *.example1.com

当前只支持用通配符来匹配域名,比如下面:

  1. Access-Control-Allow-Origin: *

尽管浏览器可以支持通配符,但是不能同时将凭证标志设置成true。
就像下面这种头部配置:

  1. Access-Control-Allow-Origin: *
  2. Access-Control-Allow-Credentials: true

这样配置浏览器将会报错,因为在响应具有凭据的请求时,服务器必须指定单个域,所不能使用通配符。简单的使用通配符将有效的禁用“Access-Control-Allow-Credentials”这个字段。
这些限制和行为的结果就是许多CORS的实现方式是根据“Origin”这个头部字段的值来生成“AccessControl-Allow-Origin”的值