日本安全研究员Masato Kinugawa在谷歌搜索中发现了一个XSS漏洞。该漏洞可带来严重风险,而且可帮助恶意人员实施钓鱼等攻击。该漏洞是由几个月前开源的JavaScript库Closure更改但未能正确过滤用户输入所引发的,可能还影响谷歌的其它产品。

Closure是由谷歌为复杂且可扩展的web应用程序而设计的广泛的JavaScript库。谷歌将其开源并用于很多应用程序中,包括搜索、Gmail、地图和Docs等产品中。

该漏洞是在2018年9月26日引入的,当时据称因为某些用户接口设计问题,有人删除了一个过滤机制。2019年2月22日,修复了该问题,方法是撤销了之前9月份做的修改。谷歌据称在发现问题不久后修复了该漏洞。

开发人员在版本回滚后发表的评论证实了该问题和HTML过滤器相关,它触发了谷歌Web Server (GWS)软件中的XSS缺陷。

LiveOverflow公司表示,虽然对该缺陷的分析集中在谷歌搜索产品,但该漏洞还可能影响使用Closure库的其它谷歌产品。

目前尚不清楚谷歌是否为该漏洞发布赏金。目前Masato Kinugawa尚未回应。我们将持续关注事件动态。

具体详情

如下内容是360代码卫士团队根据该研究员发布的视频整理而成。

Part 1

谷歌搜索无疑是互联网的首页。数十年来,谷歌搜索栏也经过了各种不受信任的用户输入的千锤百炼。而这也是我从未想到自己会亲身经历谷歌搜索中的实时XSS漏洞问题,直到我看到了如下的URL:

https://www.google.de/search?q=<noscript><p+title%3D”</noscript><img+src%3Dx+onerror%3Dalert(1)>”>cad=h

我在搜索栏中看到了q形参中诡异的字符串

“<noscript><p+title%3D”</noscript><img+src%3Dx+onerror%3Dalert(1)>”>”

之后我点击进文本框alert(1)。我的同事Masato Kinugawa实际上完成了它。而这也是一个很有意思的bug。

要理解这个bug,我们首先需要理解一些背景知识。如果你了解反射型或存储型XSS的基础知识,那么你就会知道我们通常建议你根据不受信任的数据在页面中所处的位置来正确地解码任何不受信任的数据。它只是存在于DOM中吗?还是存在于javascript内部?还是存在于作为所引用属性的SVG中等等。单是解决这个任务就并不容易,而且我们仍然会看到因此而产生的很多XSS问题,但如果你使用了适当的web框架,那么这个问题也基本上可以解决。

然而,我们在清洁HTML方面仍然面临着巨大的挑战。有时候web应用程序想要允许某些类型的标签。你想要粗体的斜体文本但不想要脚本。一个典型案例就是webmailer中展示并渲染(rendered)的HTML邮件。或者使用WYSIWYG编辑器来修改文本的风格,但你不想在其中插入活跃的元素如javascript。

而我现在告诉你的可能和直觉有点相反。至少,当我刚开始想到这一点时,我觉得不可思议,肯定是哪里出错了。我们实际上想要在客户端、浏览器、JavaScript中进行清洁。但为什么呢?难道不是应该在将不受信任的数据放在网页之前,在服务器上使用一些HTML解析器库,然后再清洁不受信任的数据吗?遗憾的是,在你想要清洁HTML并允许某些标签的用例中,你想要将XSS防御措施转移到JavaScript中。

我们看个例子,来更好地理解做到这一点的难处。试着解释一下浏览器会如何理解如下的两个HTML片段,层级(hierarchy)是什么样的?尤其是在标签看似已经结束,但它同时是另外一个标签的属性的情况下,浏览器会怎么做?

示例(1)

<div><script title = "</div>">

示例(2)

<script><div title= "</script>">

首先,这是某种不合法的HTML,对吗?因为不存在html、head或body元素。而更糟糕的是,打开的标签缺少结束标签。那么我们现在怎么做呢?浏览器可能会因为HTML出错而拒绝展示网站,或者浏览器可能会试着修复这个问题。那么让我们在浏览器中加载这两个片段,然后检查解析后的HTML即DOM树。第(1)个示例是一个div,内部存在一个script标签,而且它实际上看似非常合乎逻辑。Div打开,script打开,script标签具有一个属性title并且包含该字符串。这个字符串碰巧是一个结束的div标签,但这没关系。它存在于属性字符串中,对吧?之后浏览器修复了这个问题,方法是增加了script的关闭标签、div关闭标签,然后整个嵌入到一个常规的html文档中。

再来看第(2)个示例。它存在一个script标签,其中包含一个div标签。但这不合理啊,因为一个script标签可以包含javascript代码而非其它的HTML标签。所以,但我们查看浏览器所做的事情时,我们看到它将script标签放到了head,而script的源代码(本质上是javascript)就是div标签。之后我们看到了script的关闭标签,而引用(quote)和尖括号就是script标签之后的文本,因此它被当做文本放到body中。

因此,虽然这两个示例的设置类似,但是你会看到两个诡异的事情。首先,我们看到浏览器修复了这个HTML,方法是添加了确实的关闭标签。其次,我们看到了诡异的解析行为。因为当我们人类看到示例(2)时,我们认为它就是script标签中的一个div标签。但这不合理,因为浏览器预期的是标签内的javascript。这样它将HTML解析器中的解析器转换为javascript解析器。而现在其后的字符被解释为javascript源代码,至少在我们到达script关闭标签之前是这样的。因此script就此结束是合乎逻辑的。

从中可以看到,浏览器是非常诡异的,解析HTML绝非易事。而这种行为实际上被XSS类所滥用,即突变XSS (mutation XSS)。HTML标准中包含了其中某些突变或行为,而其它是某种浏览器所独有的。这样,实现服务器的清洁器库、试图掩藏所有浏览器版本中每种浏览器的行为是不合理的。很可能这种情况无法维持。

Part 2

而在此我们讨论下客户端清洁。基本的理念是,我们实际上能够使用浏览器自带的解析器来解析字符串之后再清洁。这想法不错吧?现在需要让浏览器以某种方式解析HTML字符串,而不会执行其中所嵌入的脚本,做到这一点并非易事。不过这里存在一些技巧。幸运的是,现在已经存在一种绝佳的且得到维护的javascript库可以办到这一点,它叫DOMPurify。它是由Cure53公司的Mario Heiderich维护的,很巧的是,这也是Masato Kinugawa工作的地方。

我们快速浏览一下源代码看看DOMPurify可能用于解析HTML的一个例子。(源代码111行)Purify.js创建了一个“template”标签元素。它的特殊之处在哪里?我现在打开一个浏览器javascript控制台,我先创建一个div元素,之后我将把一个字符串分配到innerHTML中,而实际上这就是不受信任的用户输入。

div =document.createElement (“div”)

<div></div>

div.innerHTML= “<imgsrc=x onerror=alert(1)>”

“img src=x onerror=alert(1)”

因此,我正在使用一个典型的XSS向量,一个带有不存在图像的图像标签以及执行警报的onerror事件。当我执行这一行(div.innerHTML =“IMG src=x onerror=alert(1)>”)时,我们看到失败的图像请求且弹出alert(1)。产生这种结果的原因是,当我们执行innerHTML时,浏览器会拿走字符串并解析且解释它。他发现了这个图像标签,试图加载这个图像并除掉(fire) onerror。

我们再对template元素执行同样的动作。我们先创建template,然后将payload分配给innerHTML。然后执行,什么都没有发生,并没有出现alert(1)。

template =document.createElement(“template”)

<template>…</template>

Template.innerHTML= “imgsrc=x onerror=alert(1)>”

但我们现在可以提取template.content并列出所有的子类。我们看到一个子类,就是image标签。浏览器进行了解析,现在它存在于DOM中供我们使用。例如,我们可以确信图像标签并不存在onerror属性,我们删除了它。之后我们可以再次使用innerHTML提取清洁后的SAFE html并将其用在真实的文档中。例如我们可以将其添加到div中,当然什么都不会发生。非常聪明,对吧?这就是DOMPurify的运行方式。

Part 3

轮到说Masato了,他是一名出色的XSS研究人员而且他找到了一个诡异的浏览器或者说HTML问题,从而引发DOMPufiry中出现问题。

我们再来看看template.innerHTML例子,不过这次使用的是Masato的谷歌XSS payload。我们分配之后看看所解析的内容文档。

我们有一个noscript元素,其中有一个p标签,它的title属性带有一个字符串。这个字符串包含一个noscript关闭标签以及一个img XSS向量,但它忽视了这一点,就像我们此前说明的第(1)个示例。它被解析为了一个属性。因此现在当我们清洁时,我们看到此时就认为“它是安全的”。这里没有XSS。不会执行JavaScript。

那么我们再来将其分配给div.innerHTML。F’是什么鬼?它触发了一个加载图像的请求并弹出了alert(1)。这怎么可能发生?

我们看下div。DOM是什么情况?什么?这不合理?

这看起来和之前根本不一样啊。

它看起来就像我们之前说的第(2)个示例。Noscript打开,之后是文本(碰巧是p title),之后是noscript关闭标签。意思就是它之后的image标签现在变成了真正的HTML标签,它不再包含在属性中。

为什么div中的innerHTML解析字符串和template中的innerHTML解析的不同呢?在最初的示例中,我们理解了为何会发生不同解析的情况。Script标签行为不同的原因在于它包含JavaScript,而div标签包含其它HTML标签。

但这个案例非常诡异,因为为何同样的标签会被以两种方式解释呢?问题的答案存在于HTML官方规定中。规定指出,“如果脚本语言(Javascirpt)启用,则noscript元素不代表任何含义;如果javascript禁用,则代表其子类。它用于通过影响文档的解析方式来向支持和不支持脚本语言的浏览器user agent展示不同的标记语言。”另外文档中还说明了在Javascript被禁用以及被启用的情况下如何解析noscript元素。我们的浏览器当然启用了javascript,但结果template元素中的javascript是禁用的。因此浏览器会以不同的方式解析给定的字符串。

Part 4

而这也是谷歌案例中的情况。我们回到谷歌搜索XSS缺陷。我修改了payload并增加了一个调试语句(<noscript><p title=”</noscript><img src=xonerror=debugger;>”>),从而在执行XSS时,触发javascript调试器的爆破点。顺便说一下,当时图调试更为复杂的DOM XSS时,有必要查看下它出问题的地方和原因。我们可以看看Call Stack。往上一层,我们可以看到字符串b被分配给a的innerHTML 。

b看似已被清洁,因为浏览器已经添加了关闭标签。但如你所知,它仍然是恶意的。它触发了在此处触发了XSS。但根因是什么?

当然,谷歌以闪电般的速度修复了这个问题。我拿到XSS URL稍后的时间,谷歌就修复了这个问题。不过我旧的网页仍然是打开的。因此我提取出了旧的易受攻击的javascript代码并对比新的修复后的代码。查看了新的变化后,我们发现innerHTML被另外一个XML清洁器所取代。

因此我们也能在易受攻击的版本中设置一个爆破点并仔细查看。我们往上看一点会发现谷歌也创建了一个template元素。这是谷歌的清洁器,它的工作方式类似。

现在我们看下更多的上下文,这个javascript看似难以读取,因为变量名称很丑。

但谷歌的Javascript代码实际上是开源的。我们看下谷歌的JavaScript常用库Closure。查看修复问题的条目可知,它实际上回滚到另外一个修改。2018年9月26日,nintendo switch的粉丝做出了一个修改并删除了额外的清洁器步骤,听说是因为某些接口设计问题引发的。不管怎么说,它是问题的根因所在。

Part 5

这个XSS漏洞在谷歌javascript库中存在了5个月左右的时间,很可能其它依赖于该清洁器的谷歌产品也受影响。

这真是一个疯狂的XSS漏洞。虽然我爱用“震惊”这个词语,但此时除了“震惊”之外,我无法表达自己的心情。

视频:https://youtu.be/lG7U3fuNw3A

本文由360代码卫士翻译自SecurityWeek

声明:本文来自代码卫士,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。