Gmail中AMP4Email所导致的XSS漏洞( 二 )


不过,关于这个问题有一个有趣的解决方案,可在基于WebKit和blink的浏览器中生效 。假设我们有两个id相同的元素:
<a id=test1>click!</a><a id=test1>click2!</a>此时访问window.test1时会得到什么呢?我期望获得第一个的值 。但是在Chromium中,我们实际上得到了一个HTMLCollection!

Gmail中AMP4Email所导致的XSS漏洞

文章插图
 
特别需要注意的是,我们可以通过索引以及id访问HTMLCollection中的特定的元素 。这意味着window.test1.test1实际上指的第一个元素 。我们也可以设置name属性,实现相同的效果:
<a id=test1>click!</a><a id=test1 name=test2>click2!</a>现在我们可以通过window.test1.test2访问第二个anchor元素 。
Gmail中AMP4Email所导致的XSS漏洞

文章插图
 
现在,我们回到eval(''+window.test1.test2),最后的答案是:
<a id="test1"></a><a id="test1" name="test2" href=https://www.isolves.com/it/aq/wl/2019-11-20/"x:alert(1)">那么,我们如何在AMP4Email中使用呢?
实施攻击上文提到,通过向元素添加id属性,AMP4Email很有可能会被攻击 。为了找到一些攻击的条件,我决定查看一下window的属性 。
Gmail中AMP4Email所导致的XSS漏洞

文章插图
 
AMP4Email实际上也使用了一些针对DOM Clobbering的安全措施,例如它严格禁止了id属性的某些值 。
Gmail中AMP4Email所导致的XSS漏洞

文章插图
 
但是,AMP_MODE并没有被限制 。所以让我们看看<a id=AMP_MODE>会发生什么 。
Gmail中AMP4Email所导致的XSS漏洞

文章插图
 
貌似AMP4Email试图加载某些Js文件,但由于404并未能成功 。我们可以看到URL的中间有一个undefined,对于为什么会发生这种情况,我只能想出一个解释:AMP试图获得AMP_MODE属性,并将其插入URL中 。但由于DOM Clobbering,导致预期的属性缺失 。相关代码如下:
f.preloadExtension = function(a, b) { "amp-embed" == a && (a = "amp-ad"); var c = fn(this, a, !1); if (c.loaded || c.error) var d = !1; else void 0 === c.scriptPresent && (d = this.win.document.head.querySelector('[custom-element="' + a + '"]'), c.scriptPresent = !!d), d = !c.scriptPresent; if (d) { d = b; b = this.win.document.createElement("script"); b.async = !0; yb(a, "_") ? d = "" : b.setAttribute(0 <= dn.indexOf(a) ? "custom-template" : "custom-element", a); b.setAttribute("data-script", a); b.setAttribute("i-amphtml-inserted", ""); var e = this.win.location; t().test && this.win.testLocation && (e = this.win.testLocation); if (t().localDev) { var g = e.protocol + "//" + e.host; "about:" == e.protocol && (g = ""); e = g + "/dist" } else e = hd.cdn; g = t().rtvVersion; null == d && (d = "0.1"); d = d ? "-" + d : ""; var h = t().singlePassType ? t().singlePassType + "/" : ""; b.src = https://www.isolves.com/it/aq/wl/2019-11-20/e + "/rtv/" + g + "/" + h + "v0/" + a + d + ".js"; this.win.document.head.AppendChild(b); c.scriptPresent = !0 } return gn(c) }下面是精简版:
var script = window.document.createElement("script");script.async = false;var loc;if (AMP_MODE.test && window.testLocation) { loc = window.testLocation} else { loc = window.location;}if (AMP_MODE.localDev) { loc = loc.protocol + "//" + loc.host + "/dist"} else { loc = "https://cdn.ampproject.org";}var singlePass = AMP_MODE.singlePassType ? AMP_MODE.singlePassType + "/" : "";b.src = https://www.isolves.com/it/aq/wl/2019-11-20/loc + "/rtv/" + AMP_MODE.rtvVersion; + "/" + singlePass + "v0/" + pluginName + ".js";document.head.appendChild(b);在第一行中,新建了一个script元素,然后检查AMP_MODE.test和window.testLocation是否为真 。如果AMP_MODE.localDev为真,则用window.testLocation生成URL 。最后将一些其他属性联合起来,形成完整的URL 。但由于编码方法的缺陷以及DOM Clobbering,我们可以控制最后的URL 。我们假设AMP_MODE.localDev和AMP_MODE.test都为真,进一步简化代码:
var script = window.document.createElement("script");script.async = false;b.src = https://www.isolves.com/it/aq/wl/2019-11-20/window.testLocation.protocol + "//" +window.testLocation.host + "/dist/rtv/" +AMP_MODE.rtvVersion; + "/" +(AMP_MODE.singlePassType ? AMP_MODE.singlePassType + "/" : "") +"v0/" + pluginName + ".js";document.head.appendChild(b);


推荐阅读