本文讨论的是我在2019 年8 月向Google 报告的XSS 漏洞,该漏洞与Gmail 中的AMP4Email 有关。这也是DOM Clobbering攻击技术的具体实现。
什么是AMP4Email AMP4Email(也称为动态电子邮件)是Gmail 的一项新功能,允许电子邮件包含动态HTML 内容。尽管很早以前就可以编写包含HTML 标记的电子邮件,但这通常只包含静态内容,即一些格式、图像等,没有任何脚本或表单。 AMP4Email 意味着更进一步,向电子邮件添加更多动态内容。在Google官方的一篇文章中,有完整的总结:
使用动态电子邮件,您可以轻松地直接对邮件本身执行操作,例如回复事件、填写调查问卷、浏览目录或评论等。以Google 文档中的评论为例。当有人在评论中提及您时,您不会收到单独的电子邮件。相反,您可以直接从消息中回复评论(您可以观察Gmail 分出一条线索)。然而,这一功能也引发了一些明显的安全问题,其中最明显的就是XSS。在电子邮件中插入动态内容是否意味着我们可以轻松注入任意JavaScript 代码?不,这实际上并不容易。
AMP4Email 有一个强大的验证器。简而言之,它有一个强大的白名单,可以限制某些HTML 标签的使用。具体情况可以通过https://amp.gmail.dev/playground/查看(如下图),也可以给自己发一封动态邮件看看效果如何!
当您尝试添加恶意HTML 标记时,您会看到错误。
当我尝试各种方法来绕过安全限制时,我注意到我可以将id 属性写入标签。
这似乎是一个很好的起点,因为创建包含自定义id 属性的HTML 元素可能会导致DOM 破坏。
DOM 破坏
DOM 破坏是Web 浏览器的一项旧功能,可能会给许多应用程序带来麻烦。一般来说,当您创建一个元素(例如)然后想要从javascript 引用它时,通常使用document.getElementById('username') 或document.querySelector('#username') 。但这些还不是全部方法!
传统的方法是通过全局window对象的属性来访问,比如window.username。所以在上面的例子中,这与document.getElementById('username') 完全相同!如果此时应用的某些操作完全基于某些全局变量,则可能会导致DOM Cloberring!
为了进一步分析DOM Clobbering,假设我们有以下javascript 代码:
if (window.test1.test2) { eval(''+window.test1.test2)} 此时我们的目标是只使用DOM Clobbering 技术来实现JS 代码。为此,我们需要解决以下两个问题:
我们知道我们可以为window 创建新属性,但是我们可以在其他对象(例如test1.test2)上创建新属性吗?我们可以控制DOM 元素转换为字符串的过程吗?大多数HTML 元素在转换为字符串时都会生成类似[object HTMLInputElement] 的结果。让我们从第一个问题开始,最常见的解决方案是使用而为了解决第二个问题,我编写了一段简短的JS 代码,它遍历HTML 中所有可能的元素,检查它们的toString 方法是否继承自object。如果没有,可能会返回其他对象。代码如下所示:
object.getOwnPropertyNames(window).filter(p=p.match(/Element$/)).map(p=window[p]).filter(p=p p.prototype p.prototype.toString !==对象。 prototype.toString) 上面的代码返回两个元素:HTMLAreaElement() 和HTMLAnchorElement()。在AMP4Email中,第一个不在白名单中,因此只能使用第二个。其中,toString函数只返回href属性的值:
因此,如果我们想要执行攻击(通过window.test1.test2的值),我们需要类似于以下的代码:
但问题是它根本不起作用。 test1.test2 未定义。只需将其替换为.
然而,这个问题有一个有趣的解决方案,可以在WebKit 和基于眨眼的浏览器中运行。假设我们有两个具有相同id 的元素:
click!click2!此时访问window.test1会得到什么?我希望得到第一个的值。但在Chromium 中我们实际上得到了一个HTMLCollection!
特别是,我们可以通过索引和id 访问HTMLCollection 中的特定元素。这意味着window.test1.test1 实际上引用了第一个元素。我们还可以设置name属性来达到同样的效果:
点击!点击2!现在我们可以通过window.test1.test2访问第二个锚元素。
现在,我们回到eval(''+window.test1.test2),最终的答案是:
那么,我们如何在AMP4Email 中使用它呢?
实施攻击如上所述,AMP4Email 很可能通过向元素添加id 属性来受到攻击。为了找到一些攻击条件,我决定检查窗口的属性。
AMP4Email 实际上对DOM Clobbering 使用了一些安全措施,例如它严格禁止id 属性的某些值。
然而,AMP_MODE 不受限制。那么让我们看看会发生什么。
看来AMP4Email尝试加载一些Js文件,但由于404而失败。我们可以看到URL中间有一个未定义的内容,对于为什么会发生这种情况我只能想出一个解释: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;如果(d) { d=b; b=this.win.document.createElement('script'); b.async=!0; yb(a, '_') d='' : b.setAttribute(0=dn.indexOf(a) '自定义模板' : '自定义元素', a); b.setAttribute('数据脚本', a); b. setAttribute('i-amphtml-插入', ''); 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=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=loc + '/rtv/' + AMP_MODE.rtvVersion; + '/' + singlePass + 'v0/' + pluginName + '.js';document.head.appendChild(b);在第一行中,创建了一个新的脚本元素,然后检查AMP_MODE.test 和window.testLocation 是否为true。如果AMP_MODE.localDev 为true,则使用window.testLocation 生成URL。最后一些其他属性组合起来形成完整的URL。但由于编码方法和DOM Clobbering 的缺陷,我们可以控制最终的URL。我们假设AMP_MODE.localDev 和AMP_MODE.test 都为true,以进一步简化代码:
var script=window.document.createElement('script');script.async=false;b.src=window.testLocation.protocol + '//' + window.testLocation.host + '/dist/rtv/' + AMP_MODE .rtv版本; + '/' + (AMP_MODE.singlePassType AMP_MODE.singlePassType + '/' : '') + 'v0/' + pluginName + '.js';document.head.appendChild(b);还记得之前的window.test1.test2,我们只是做同样的事情并重写window.testLocation.protocol:
但实际情况下,由于Content-Security-Policy部署在AMP中,因此代码并没有被执行:
Content-Security-Policy: default-src '无'; script-src 'sha512-oQwIl.==' https://cdn.ampproject.org/rtv/https://cdn.ampproject.org/v0.js https://cdn.ampproject.org/v0/所以后来我开始寻找绕过CSP的方法,当然,我成功找到了它:https://twitter.com/SecurityMB/status/1162690916722839552。但我发现2016年就有人发现了同样的方法。
如果您想了解更多信息,请点击此处。
时间表2019 年8 月15 日 向Google 发送报告
2019 年8 月16 日 得到回复
2019 年9 月10 日 Google 确认漏洞
2019 年10 月12 日 Google 确认漏洞已修复
2019 年11 月18 日 详情公布
本文由白浩辉编译。不代表百豪汇任何观点或立场。
资料来源:https://nosec.org/home/detail/3188.html
原文:https://research.securitum.com/xss-in-amp4email-dom-clobbering/
百浩汇从事信息安全领域,专注于安全大数据和企业威胁情报。
公司产品:FOFA-网络空间安全搜索引擎、FOEYE-网络空间检索系统、NOSEC-安全信息平台。
为您提供:网络空间测绘、企业资产采集、企业威胁情报、应急响应服务。