文章详情
通过命名空间混淆实现突变XSS-DOMPurify
在这篇博文中,我将解释我最近在DOMPurify--流行的HTML过滤库中的绕过。简而言之,DOMPurify的工作是将一个不受信任的HTML片段删除所有可能导致跨站点脚本(XSS)的元素和属性。
这是Bypass:
<form>
<math><mtext>
</form><form>
<mglyph>
<style></math><img src onerror=alert(1)>
相信我,这段话中没有一个元素是多余的。
为了理解为什么这段代码能够工作,我需要给你介绍一下HTML规范中的一些有趣的功能,我使用这些功能来进行Bypass工作。
DOMPurify的用法
我们先从基础知识开始,解释一下DOMPurify通常是如何使用的。假设我们在htmlMarkup中有一个不受信任的HTML,我们想把它分配给某个div,我们使用下面的代码来使用DOMPurify对它进行过滤并分配给div。
div.innerHTML = DOMPurify.sanitize(htmlMarkup)
就 HTML 的解析和序列化以及对 DOM 树的操作而言,在上面的简短片段中发生了以下操作。
- 1.htmlMarkup被解析到DOM树中。
- 2.DOMPurify对DOM树进行过滤(简而言之,这个过程就是要走遍DOM树中的所有元素和属性,并删除所有不在允许列表中的节点)。
- 3.DOM树被序列化回HTML标记。
- 4.分配到innerHTML后,浏览器再次解析HTML标记。
- 5.解析后的DOM树被追加到文档的DOM树中。
让我们在一个简单的例子上看看。假设我们的初始标记是A<img src=1 onerror=alert(1)>B。在第一步中,它被解析成以下的树。
然后,DOMPurify对其进行过滤,留下以下DOM树。
然后将其序列化为。
A<img src="1">B
而这就是DOMPurify.sanitize返回的内容。然后在分配给innerHTML时,浏览器会再次解析这些标记。
该DOM树与DOMPurify工作的DOM树相同,然后将其附加到文档中。
所以简而言之,我们的操作顺序如下:解析➡️序列化➡️解析。按照直觉可能是序列化一棵DOM树并再次解析它应该总是返回初始的DOM树。但事实完全不是这样。在HTML规范中甚至有一节关于序列化HTML片段的警告。
It is possible that the output of this algorithm [serializing HTML], if parsed with an HTML parser, will not return the original tree structure. Tree structures that do not roundtrip a serialize and reparse step can also be produced by the HTML parser itself, although such cases are typically non-conforming.
重要的启示是,序列化-解析前后并不能保证返回原始DOM树(这也是被称为mXSS(突变XSS)的根本原因)。虽然通常这些情况是由于某种解析器/序列化器错误造成的,但至少有两种符合规范的变种情况。
嵌套FORM元素
其中一种情况与FORM元素有关。在HTML中,它是一个相当特殊的元素,因为它本身不能嵌套。规范中明确规定,它不能嵌套FORM为其子元素。
这可以在任何浏览器中确认,并使用以下标记。
<form id=form1>
INSIDE_FORM1
<form id=form2>
INSIDE_FORM2
这将产生以下DOM树。
第二种形式在DOM树中完全被省略了,就像它从来没有出现过一样。
现在是有趣的部分。如果我们继续阅读HTML规范,它实际上给出了一个例子,说明只要有一个稍有破绽的标记和错误的嵌套标签,就有可能创建嵌套表单。这里是(直接摘自规范)。
<form id="outer"><div></form><form id="inner"><input>
它产生了以下DOM树,其中包含一个嵌套的表单元素。
这不是任何特定浏览器的bug,而是直接来自HTML规范,并在解析HTML的算法中进行了描述。下面是大意。
- 当你打开一个< form>标签时,解析器需要记录它是用一个表单元素指针打开的(规范中是这样称呼的)。如果指针不是空的,那么就不能创建表单元素。
- 当你结束一个< form>标签时,表单元素指针总是被设置为null。
因此,回到这个片段。
<form id="outer"><div></form><form id="inner"><input>
一开始,表单元素指针被设置为id="external"的那个。然后是一个div,</form>结束标签将表单元素指针设置为null。因为它是空的,所以可以创建下一个id="inner"的表单;而且因为我们当前在div中,所以我们实际上有一个嵌套在表单中的表单。
现在,如果我们尝试序列化产生的DOM树,我们将得到以下标记。
<form id="outer"><div><form id="inner"><input></form></div></form>
注意,这个标记不再有任何错误嵌套的标记。而当再次解析该标记时,就会创建以下DOM树。
所以这就是一个证明,序列化-再解析 前后并不能保证返回原始DOM树。而更有趣的是,这基本上是一个符合规范的突变。
自从我意识到这个怪癖的那一刻起,我就非常确定,一定可以通过某种方式滥用它来绕过HTML sanitizers。而在很长时间没有得到任何利用它的想法后,我终于偶然发现了HTML规范中的另一个怪癖。不过在说具体的怪癖本身之前,先说说我最喜欢的HTML规范的潘多拉盒子:
外部内容
外部内容就像一把瑞士军刀,可以用来突破解析器和过滤器。我在之前的DOMPurify绕过以及Ruby sanitize库的绕过中使用了它。
HTML解析器可以创建一个包含三个命名空间元素的DOM树。
- HTML命名空间(http://www.w3.org/1999/xhtml)
- SVG命名空间(http://www.w3.org/2000/svg)
- MathML命名空间(http://www.w3.org/1998/Math/MathML)
默认情况下,所有的元素都在HTML命名空间;但是如果解析器遇到< svg>或< math>元素,那么它就会分别 "切换 "到SVG和MathML命名空间。而这两个命名空间都会产生外来内容。
在外来内容标记中,与普通HTML中的解析方式不同。这一点在< style>元素的解析上可以最清楚的表现出来。在HTML命名空间中,< style>只能包含文本,不能有子元素,而且HTML实体不被解码。而在外来内容中就不一样了:外来内容的< style>可以有子元素,实体也会被解码。
考虑以下标签。
<style><a>ABC</style><svg><style><a>ABC
它被解析成以下的DOM树。
注意:从现在开始,本博文中DOM树中的所有元素都将包含一个命名空间。所以html style意味着它是HTML命名空间的< style>元素,而svg style意味着它是SVG命名空间的< style>元素。
由此产生的DOM树证明了我的观点:html style只有文本内容,而svg style则像普通元素一样被解析。
继续往下看,可能很想做某个观察。那就是:如果我们在<svg>或<math>里面,那么所有的元素也都在非HTML命名空间。但事实并非如此。在HTML规范中,有一些元素叫做</math></svg>MathML文本集成点和HTML集成点。而这些元素的子元素都有HTML命名空间(下面我列举了某些例外)。
请看下面的例子。
<math>
<style></style>
<mtext><style></style>
它被解析成以下DOM树。
请注意,作为math直接子元素的style元素是在MathML命名空间,而mtext中的style元素是在HTML命名空间。而这是因为mtext是MathML文本集成点,并使解析器切换命名空间。
MathML文本集成点是。
- math mi
- math mo
- math mn
- math ms
HTML集成点是:
- math annotation-xml 如果它有一个叫做编码的属性,其值等于text/html 或application/xhtml+xml
- svg foreignObject
- svg desc
- svg title
我一直以为MathML文本集成点或HTML集成点的子元素都默认有HTML命名空间。我是真是大错特错! HTML规范中说,MathML文本集成点的子节点默认为HTML命名空间,但有两个例外:mglyph和malignmark。而且只有当它们是MathML文本集成点的直接子元素时才会出现这种情况。
我们用下面的标签来检查一下。
<math>
<mtext>
<mglyph></mglyph>
<a><mglyph>
请注意,作为mtext的直接子元素的mglyph是在MathML命名空间,而作为html a元素的子元素的mglyph是在HTML命名空间。
假设我们有一个 "当前元素",我们想确定它的命名空间。我整理了一些经验法则。
- 当前元素在其父元素的命名空间中,除非满足以下几点条件。
- 如果当前元素是< svg>或< math>,而父元素在HTML命名空间,那么当前元素分别在SVG或MathML命名空间。
- 如果当前元素的父元素是HTML集成点,则当前元素在HTML命名空间,除非是< svg>或< math>。
- 如果当前元素的父元素是MathML集成点,那么当前元素在HTML命名空间,除非它是< svg>、< math>、< mglyph>或< malignmark>。
- 如果当前元素是< b>、< big>、< blockquote>、< body>、< br>、< center>、< code>、< dd>、< div>、< dl>、< dt>、< em>、< embed>、< h1>之一。< h2>, < h3>, < h4>, < h5>, < h6>, < head>, < hr>, < i>, < img>, < li>, < listing>, < menu>, < meta>, < nobr>, < ol>, < p>, < pre>, < ruby>, < s>, < small>。< span>、< strong>、< strike>、< sub>、< sup>、< table>、< tt>、< u>、< ul>、< var>或< font>,并定义了颜色、面或大小属性,那么,堆栈上的所有元素都会被关闭,直到看到MathML文本整合点、HTML整合点或HTML命名空间中的元素。然后,当前元素也在HTML命名空间。
当我在HTML规范中找到这个关于mglyph的宝石时,我立刻知道这就是我一直在寻找的滥用html形式突变绕过sanitizer的方法。
DOMPurify bypass
所以让我们回到绕过DOMPurify的Payload。
<form><math><mtext></form><form><mglyph><style></math><img src onerror=alert(1)>
payload利用错误嵌套的html表单元素,并包含mglyph元素。它产生的DOM树如下。
这个DOM树是无害的。所有元素都在DOMPurify的允许列表中。注意,mglyph是在HTML命名空间。而那个看起来像XSS payload的片段只是html样式中的一个文本。因为有一个嵌套的html形式,我们可以很肯定这个DOM树在rearsing时是会被突变的。
所以DOMPurify在这里没有任何作用,而是返回一个序列化的HTML。
<form><math><mtext><form><mglyph><style></math><img src onerror=alert(1)></style></mglyph></form></mtext></math></form>
这个片段有嵌套的表单标签。所以当它被分配给innerHTML时,它被解析成以下DOM树。
因此,现在第二个 html 表单没有创建,mglyph 现在是 mtext 的直接子元素,这意味着它在 MathML 命名空间中。正因为如此,style也在MathML命名空间中,因此它的内容不被视为文本。然后,</math>关闭了< math>元素,现在img是在HTML命名空间中创建的,从而导致XSS。
总结
综上所述,这个绕过之所以能够实现,是因为几个因素。
- DOMPurify的典型用法使得HTML标记被解析两次。
- HTML规范有一个怪癖,使得创建嵌套表单元素成为可能。但是,在重新解析的时候,第二个表单会消失。
- mglyph和malignmark是HTML规范中的特殊元素,在某种程度上,如果它们是MathML文本集成点的直接子元素,那么它们就属于MathML命名空间,尽管其他标签默认都属于HTML命名空间。
- 利用以上这些方法,我们可以创建一个标记,其中有两个表单元素和mglyph元素,这些元素最初是在HTML命名空间,但在重新解析时却在MathML命名空间,使得后续的样式标签要进行不同的解析,导致XSS。
Cure53对我的Bypass推送更新后,又发现了一个。
<math><mtext><table><mglyph><style><math><table id="</table>"><img src onerror=alert(1)">
我把它留给读者,让读者自己去弄清楚为什么这个payload能用。提示:根本原因和我发现的bug一样。
这个bypass也让我意识到,以下形式
div.innerHTML = DOMPurify.sanitize(html)
是容易发生突变XSS的设计,再找一个实例只是时间问题。我强烈建议给DOMPurify传递RETURN_DOM或RETURN_DOM_FRAGMENT选项,这样就不会执行序列化-解析的往返操作。
最后说明一下,我在为即将到来的XSS学院远程培训准备材料时发现了DOMPurify绕过。虽然还没有正式宣布,但细节(包括议程)将在两周内公布。我将讲授有趣的XSS技巧,重点是打破解析器和过滤器。如果你已经知道你有兴趣,请联系我们training@securitum.com,我们将为你预定座位!
作者:Michał Bentkowski Michał Bentkowski
原文地址:https://research.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass/
上一文章:Tomcat进程注入技术复现
下一文章:红蓝对抗系列之浅谈蓝队反制红队的手法一二
相关推荐
- 10-21由Arduino制作badusb联动cs进行一插上线
- 10-21ActiveDirectoryPowerShell
- 05-14爬虫的入门了解(爬虫技术可以做什么)
- 10-21Pickle反序列化源码分析与漏洞利用
- 10-22一次简单的内网渗透靶场实验
- 05-11面向协议编程依赖什么原则(与面向对象编程的
- 05-10NUAACTF2021Easy_XSS复现
- 05-08记一次对Tp二开的源码审计(Php审计)
- 10-21CVE-2020-15148Yii2反序列化RCEPOP链分析
- 10-21fastadmin后台注入分析
- 10-30【实战】无回显Rce到Getshell
- 10-20SEO赚钱技术和SEO赚钱思维
- 10-29AKSK命令执行到谷歌验证码劫持
- 05-14为什么TCP需要三次握手(分析TCP三次握手过程