在这篇博文中,我将解释我最近在DOMPurify(流行的HTML杀毒程序库)中的绕过。简而言之,DOMPurify的工作是获取假定来自最终用户的不受信任的HTML片段,并删除可能导致跨站点脚本(XSS)的所有元素和属性。
相信我,这段代码中没有一个元素是多余的🙂。
要理解这段特定代码为什么能工作,我需要带您了解一下HTML规范的一些有趣特性,这些特性是我用来使旁路工作的。
让我们从基础知识开始,然后解释DOMPurify通常是如何使用的。假设我们在htmlMarkup中有一个不受信任的HTML,并且我们希望将其分配给某个div,我们使用以下代码使用DOMPurify对其进行清理,并将其分配给div:
就解析和序列化HTML以及DOM树上的操作而言,上面的简短代码段中发生了以下操作:
DOMPurify清理DOM树(简而言之,这个过程是遍历DOM树中的所有元素和属性,并删除不在允许列表中的所有节点)。
让我们通过一个简单的例子来看看这一点。假设我们的初始标记为A<;img src=1 onerror=alert(1)>;B。在第一步中,它被解析到以下树中:
这是DOMPurify.sanitize返回的内容。然后,浏览器在分配给innerHTML时再次解析该标记:
DOM树与DOMPurify处理的树相同,然后将其附加到文档中。
因此,简而言之,我们有以下操作顺序:解析➡️序列化➡️解析。直觉可能是序列化DOM树并再次解析它应该总是返回初始的DOM树。但这完全不是真的。HTML规范中甚至有一节关于序列化HTML片段的警告:
如果使用HTML解析器进行解析,则此算法[序列化HTML]的输出可能不会返回原始的树结构。不往返序列化和重新解析步骤的树结构也可以由HTML解析器本身产生,尽管这种情况通常是不一致的。
重要的是,序列化-解析往返不能保证返回原始的DOM树(这也是一种称为突变XSS的XSS的根本原因)。虽然这些情况通常是某种解析器/序列化程序错误的结果,但至少有两种符合规范的突变情况。
其中一个案例与表单元素相关。它在HTML中是非常特殊的元素,因为它本身不能嵌套。规范明确规定,它不能有任何也是以下形式的后代:
第二种形式在DOM树中被完全省略,就像它以前不在那里一样。
现在有趣的部分来了。如果我们继续阅读HTML规范,它实际上给出了一个例子,使用带有错误嵌套标记的稍微破碎的标记,就有可能创建嵌套表单。下面是它(直接取自规范):
这不是任何特定浏览器中的错误;它直接由HTML规范产生,并在HTML解析算法中进行了描述。大意是这样的:
当您打开<;form>;标记时,解析器需要记录它是用表单元素指针打开的(规范中就是这样调用的)。如果指针不为空,则无法创建表单元素。
当您结束<;form>;标记时,表单元素指针始终设置为NULL。
在开始时,表单元素指针被设置为id=";外层";的指针。然后,启动一个div,并且<;/form>;结束标记将表单元素指针设置为NULL。因为它是空的,所以可以创建id=";Internal";的下一个表单;而且因为我们当前在div中,所以我们实际上在表单中嵌套了一个表单。
现在,如果我们尝试序列化生成的DOM树,我们将获得以下标记:
请注意,此标记不再有任何错误嵌套的标记。当再次解析标记时,将创建以下DOM树:
因此,这证明不能保证串行化-重解析往返返回原始DOM树。更有趣的是,这基本上是一种符合规范的突变。
从我意识到这个怪癖的那一刻起,我就相当确信,一定有可能以某种方式滥用它来绕过HTML消毒器。在很长一段时间没有任何关于如何使用它的想法之后,我终于偶然发现了HTML规范中的另一个怪癖。但是在进入特定的怪癖本身之前,让我们先来谈谈我最喜欢的HTML规范的潘多拉盒子:外来内容。
外来内容就像一把瑞士军刀,用来破坏解析器和消毒器。我在之前的DOMPurify旁路以及Ruby Sanitize库的旁路中使用过它。
默认情况下,所有元素都在HTML名称空间中;但是,如果解析器遇到<;svg>;或<;ath>;元素,则会分别“切换”到SVG和MathML名称空间。这两个名称空间都会生成外来内容。
在外来内容中,标记的解析方式与普通HTML不同。这可以在解析<;style>;元素时表现得最清楚。在HTML命名空间中,<;style>;只能包含文本;没有子体,也不解码HTML实体。外来内容则不是这样:外来内容的<;style>;可以具有子元素,并且实体被解码。
注意:从现在开始,这篇博客的DOM树中的所有元素都将包含一个名称空间。因此,html style表示它是HTML名称空间中<;style>;元素,而svg style表示它是SVG名称空间中的<;style>;元素。
生成的DOM树证明了我的观点:HTML样式只有文本内容,而SVG样式的解析方式与普通元素一样。
接下来,我们可能会忍不住要进行某种观察。也就是说:如果我们在<;SVG&>;或<;Math>;中,那么所有元素也都在非HTML名称空间中。但事实并非如此。HTML规范中有一些元素称为MathML文本集成点和HTML集成点。这些元素的子元素都有HTML名称空间(除了下面列出的某些例外)。
请注意,作为数学的直接子元素的style元素位于MathML名称空间中,而mtext中的style元素位于HTML名称空间中。这是因为mtext是MathML文本集成点,并使解析器切换名称空间。
数学批注-XML,如果其值等于text/html或application/xhtml+xml,则为XML。
我总是假设默认情况下MathML文本集成点或HTML集成点的所有子对象都有HTML名称空间。我大错特错了!HTML规范指出,MathML文本集成点子级默认使用HTML名称空间,但有两个例外:mglyph和恶意标记。只有当它们是MathML文本集成点的直接子对象时,才会发生这种情况。
请注意,mtext的直接子项mglyph位于MathML名称空间中,而作为html a元素的子项的mglyph位于HTML名称空间中。
假设我们有一个“当前元素”,并且我们想确定它的名称空间。我整理了一些经验法则:
除非满足以下各点的条件,否则当前元素位于其父元素的命名空间中。
如果当前元素是<;SVG&>;或<;MATH&>;并且父元素位于HTML名称空间中,则当前元素分别位于SVG或MathML名称空间中。
如果当前元素的父元素是HTML集成点,则当前元素在HTML名称空间中,除非它是<;SVG&>;或<;MATH&>;。
如果当前元素的父级是MathML集成点,则当前元素在HTML命名空间中,除非它是<;SVG&>;、<;数学&>、<;mglyph>;或<;恶性标记>;。
如果当前元素是<;b>;、<;Big>;、<;block>;、<;body>;、<;body>;、<;center>;、<;code>;、<;dd>;、<;div>;、<;dl>;、<;dt>;、<;em>;、<;embed>;、<;dd>;、<;dt>;、<;,<;h2&>,<;h3>;,<;h4>;,<;h5>;,<;h6>;,<;head>;,<;hr>;,<;img>;,<;li>;,<;Listing>;,<;Menu>;,<;meta>;,<;nobr>;,<;Ol>;,<;p>;,<;prep>;,<;ruby>;,<;s>;,<;mall>;,<;span>;,<;strong>;,<;Strike>;,<;sub>;,<;sup>;,<;table>;,<;tt>;,<;u>;,<;ul>;,<;var>;或<;font>;定义了颜色、字体或大小属性,然后关闭堆栈上的所有元素,直到看到MathML文本集成点、HTML集成点或HTML命名空间中的元素。然后,当前元素也在HTML名称空间中。
当我在HTML规范中找到这篇关于mglyph的珍品时,我立刻知道这就是我一直在寻找的关于滥用html表单突变来绕过杀菌器的东西。
有效负载使用错嵌的html表单元素,并且还包含mglyph元素。它会生成以下DOM树:
这棵DOM树是无害的。所有元素都在DOMPurify的允许列表中。请注意,mglyph位于HTML名称空间中。看起来像XSS有效负载的代码片段只是html样式中的一个文本。因为有一个嵌套的html表单,所以我们可以非常确定这个DOM树在重新解析时会发生变化。
此代码段具有嵌套的Form标记。因此,将其分配给innerHTML时,会将其解析为以下DOM树:
所以现在没有创建第二个html表单,mglyph现在是mtext的直接子级,这意味着它在MathML名称空间中。正因为如此,样式也在MathML名称空间中,因此它的内容不被视为文本。然后<;/ath>;关闭<;ath>;元素,现在在HTML名称空间中创建img,这将导致XSS。
HTML规范有一个特殊之处,它使得创建嵌套的表单元素成为可能。但是,在重新解析时,第二种形式将消失。
Mglyph和恶意标记是HTML规范中的特殊元素,如果它们是MathML文本集成点的直接子元素,则它们在MathML名称空间中,即使默认情况下所有其他标记都在HTML名称空间中。
使用以上所有内容,我们可以创建一个具有两个Form元素和mglyph元素的标记,该元素最初在HTML名称空间中,但在重新解析时在MathML名称空间中,使得后续的样式标记将被不同地解析,并导致XSS。
在#TWCTF期间使用@sqrtrev@0xParrot@web_payload发现#DOMPurify库的1天mxss利用有效负载。Team@GuesserSuper<;math>;<;mtext>;<;table>;<;mglyph>;<;style>;<;math>;<;table id=“<;/TABLE>;”>;<;img src onerror=ALERT(1)“>;P.S:刚在一天前打了补丁。
-Sapra(@pwntheweb)2020年9月21日。
我把它留给读者作为练习来弄清楚这个有效负载为什么会起作用。提示:根本原因与我发现的bug相同。
在设计上很容易发生XSS-ES突变,找到另一个实例只是个时间问题。我强烈建议您将RETURN_DOM或RETURN_DOM_FACTION选项传递给DOMPurify,这样就不会执行序列化-解析往返。
最后要注意的是,在为我即将到来的名为XSS Academy的远程培训准备材料时,我发现了DOMPurify旁路。虽然还没有正式宣布,但细节(包括议程)将在两周内公布。我将讲授有趣的XSS技巧,重点是破解解析器和杀菌器。如果您已经知道您感兴趣,请通过[email protected]联系我们,我们将为您预订座位!