在这篇博文中,我将解释我最近在[DOMPurify][1]--流行的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规范中][5]甚至有一节关于序列化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为其子元素][6]。

这可以在任何浏览器中确认,并使用以下标记。

<form id=form1>
INSIDE_FORM1
<form id=form2>
INSIDE_FORM2

这将产生以下DOM树。

第二种形式在DOM树中完全被省略了,就像它从来没有出现过一样。

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖