前言
最近在挖SRC时遇到了JOSNP和CORS,但是进行JSONP劫持和CORS跨域请求的尝试却失败了,后来经过研究发现根本原因是cookie跨域的限制造成了,遂有了这篇文章。
关于cookie跨域的分析
影响cookie跨域的samesite和secure
2016年开始,Chrome 51版本对Cookie新增了一个 SameSite属性,为了防止CSRF
攻击,陆续的各大厂商的浏览器也都适配了该属性,该属性限制了在不同情况下cookie的跨域。而Secure则是规定了cookie是否可以在http下传输,如果Secure为true,则仅在使用https时才会携带cookie。
下图为不同SameSite设置的效果,当然也可以不设置SameSite,大多数浏览器在不设置SameSite时默认为Lax
指的一提的是,一般情况下如果设置SameSite为None,也要同时设置Secure,否则一些浏览器可能会拒绝仅有SameSite=None的设置。
在不同浏览器上的实验
上面的描述只是标准,具体的效果还要依赖于浏览器的实现,这里进行一波实验来验证一下上述和标准和各个浏览器的实现。
先使用小皮面板创建两个站点,test1.com和test2.com,test1.com注意设置为https访问。
在test1.com上部署一个设置cooke的前端页面test1.com/cookieSetting.html和一个后端处理脚本test1.com/getCookie.php,还有一个测试cookie是否携带的页面test1.com/json.php,如果携带cookie就会返回账户密码,否则返回error。
test1.com/cookieSetting.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cookie 设置与清除</title>
</head>
<body>
<h2>Cookie 设置与清除表单</h2>
<form action="./getCookie.php" method="post" id="cookieForm">
<label for="samesite">SameSite:</label>
<select id="samesite" name="samesite">
<option value="Strict">Strict</option>
<option value="Lax">Lax</option>
<option value="None">None</option>
<option value=" ">不设置</option>
</select>
</br>
<label for="httponly">HttpOnly:</label>
<input type="checkbox" id="httponly" name="httponly">
<label for="secure">Secure:</label>
<input type="checkbox" id="secure" name="secure">
</br>
<select id="setcookie" name="setcookie">
<option value="set">设置cookie</option>
<option value="">清除cookie</option>
</select>
</br>
<button type="submit">确认</button>
</form>
</body>
</html>
test1.com/getCookie.php
<?php
$simesite=$_POST["samesite"];
$httponly=$_POST["httponly"];
$secure=$_POST["secure"];
$set=$_POST["setcookie"];
// 检查是否存在名为 "secret" 的Cookie
if ($set==="") {
// 如果存在,则清除Cookie
setcookie('secret', '', time() - 3600); // 将过期时间设置为过去的时间
echo 'Cookie已清除';
} else {
// 如果不存在,则设置Cookie为 "secret=666666"
// 启用HTTPS时设置Secure标志
$secureFlag = true;
// 设置Cookie
setcookie('secret', '666666', [
'expires' => time() + 3600, // 0表示会话结束时过期
'path' => '/', // 可在整个域名下访问
'domain' => '', // 通过任何子域名都可以访问
'secure' => $secure, // 根据是否使用HTTPS动态设置Secure标志
'httponly' => $httponly, // 仅通过HTTP协议访问,防止JavaScript访问
'samesite' => $simesite, // 允许在跨站请求中发送Cookie
]);
echo 'Cookie已设置';
}
?>
test1.com/json.php
<?php
if(isset($_COOKIE['secret']) && $_COOKIE['secret'] == '666666') {
header("Content-Type: text/json");
echo "hack({\"username\":\"admin\",\"password\":\"123456\"})";
} else {
echo 'error';
}
?>
在test2.com则部署一个跨域测试的html页面crossSite.html,来测试各种跨域的cookie携带情况
test2.com/crossSite.html
<a href="https://test1.com/json.php?a">link to test1.com/json.com</a>
</br>
<button onclick="window.open('https://test1.com/json.php?windowOpen')">window.open("https://test1.com/json.php")</button>
</br>
<button onclick="window.location.href = 'https://test1.com/json.php?windowLocation'">window.location.href = 'https://test1.com/json.php'</button>
<form action="https://test1.com/json.php" method="get">
<input name="get" type="text hidden" value="get"/>
<button type="submit">form get</button>
</form>
<form action="https://test1.com/json.php" method="post">
<input name="post" type="text hidden" value="post"/>
<button type="submit">form post</button>
</form>
<iframe src="https://test1.com/json.php?iframe"></iframe>
<script src="https://test1.com/json.php?script"></script>
<link rel="stylesheet" href="https://test1.com/json.php?stylesheet">
<script>
fetch('https://test1.com/json.php?fetch', {
method: 'GET', // 或 'POST',根据实际需求选择
credentials: 'include' // 允许携带 Cookie
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
</script>
<script>
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://test1.com/json.php?ajax', true);
xhr.withCredentials = true; // 允许携带 Cookie
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
console.log(data);
} else {
console.error('Error:', xhr.status);
}
}
};
xhr.send();
</script>
<link rel="prefetch" href="https://test1.com/json.php?prefetch">
测试步骤很简单,先通过test1.com/cookieSetting.html设置不同属性的cookie,然后通过test2.com/crossSite.html进行跨域访问,检查其cookie携带情况即可,这里大量图片就省略了,我直接给出我的测试结果。
浏览器 | samesite=strick | samesite=lax | samesite=none secure=false | samesite=none secure=true | 不设置samesite也不设置secure |
---|---|---|---|---|---|
firefox浏览器 Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0 | 全部不携带cookie | 与标准有一处不同,<form method=post> 携带cookie | 设置成功,全部携带cookie | 与samesite=none secure=false相同 | 此时secure=false,而samesite=none。全部携带cookie。 |
edge浏览器 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0 | 全部不携带cookie | 与之前的总结完全相同,在对<form method=post> 的处理上,与firefox不同 | edge拒绝这样设置cookie | 全部携带cookie | 此时secure和samesite都没有显示值。经过测试,本文第一张图中的跨站定级跳转全部携带cookie,其他不携带,post值得注意 |
chrome浏览器 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 | 与edge一致 | 与edge一致 | 与edge一致 | 与edge一致 | 与edge一致 |
关于CSRF、JSONP和CORS的cookie跨域
经过上面的分析,在什么时候可以进行跨域其实已经很明了了
CSRF使用get和input进行跨域,当使用get进行跨域时,除了SameSite=strick的情况,都可以携带cookie;当使用post进行跨域时,按照标准,只有SameSite=None和Secure被设置时才能携带cookie。但是由于很多浏览器的实现不标准或者对于不设置SameSite的处理较为模糊,有时不设置SameSite或设置SameSite=None而不设置Secure时允许input跨域携带cookie,这要视浏览器的实现而定。
JSONP劫持依赖
<script src>
访问敏感信息,如果希望<script src>
携带cookie,只有SameSite=None的时候才可以CORS配置不当,对于CORS的设置就不多说了,在Access-Control-Allow-Credentials为true的前提下,由于CORS的跨域依赖的是ajax和fetch,要在这种情况下携带cookie进行跨域,同样也只有SameSite=None的时候才可以