利用命名空间混淆的mxss来绕过DOMPurify
本篇文章主题内容来自https://research.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass/
原文提到作者利用以下代码绕过了DOMPurify2.0.17版本。
<form> <math><mtext> </form><form> <mglyph> <style></math><img src onerror=alert(1)>
但是我实际测试的时候,DOMPurify2.0.16版本就修复了这个POC造成的xss问题。我使用的是DOMPurity的2.0.15版本进行测试。
首先什么是DOMPurify?
DOMPurify是针对HTML、MathML和SVG的仅仅支持DOM、快速、高速的XSS过滤器。
它的使用很简单。
var clean = DOMPurify.sanitize(dirty);
这样就完成对dirty数据的过滤。而过滤得到的clean数据可以直接使用innerHTML插入到DOM中。
使用DOMPurify过滤数据的过程
1)DOMPurify解析输入,将输入数据解析成DOM,对DOM节点和属性进行过滤
2)DOMPurify将过滤得到的结果转换成对应的HTML字符(类似序列化)
3)调用方得到过滤的数据,将过滤的数据插入的html的DOM
我从github上下载到DOMPurify的代码构建了测试POC如下:
<html> <div id="test"></div> <script type="text/javascript" src="dist/purify.min.js"></script> <script> str = "<form><math><mtext></form><form><mglyph><style></math><img src onerror=alert(1)>"; first_str = DOMPurify.sanitize(str); console.log(first_str) div = document.getElementById("test") div.innerHTML = first_str; </script> </html>
console.log的输入是:<form><math><mtext><form><mglyph><style></math><img src onerror=alert(1)></style></mglyph></form></mtext></math></form>
但是这段代码在HTML中的解析确是:<form><math><mtext><mglyph><style></style></mglyph></mtext></math><img src="" onerror="alert(1)"></form>
中间有个form标签不见了。导致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结构。这种情况往往是html解析器或序列化过程造成了错误。但是存在情况是由于html规范导致的。
嵌套FORM元素
html规范中,不允许form元素的子元素是form。那么说明嵌套form元素是不被允许的。这会导致嵌套里面的form元素被html解析器忽略。
html规范中给出一个错误嵌套的例子:
<form id="outer"><div></form><form id="inner"><input>
这段html会导致后面一个form变成前面一个div的子元素。也同时导致了嵌套form的情况。这不是浏览器的错误,这是html规范中说明的,它其实很好的说明的html解析算法的工作思想。
当遇到一个<form>标签的时候,解析器会记录当前正在一个打开的form标签中。在这个状态中你无法在创建form标签。但是当遇到</form>标签时,解析器认为前一个form已经结束,可以在创建一个form。这样导致了form标签的嵌套。
此时错误嵌套form的html片段为:
<form id="outer"><div><form id="inner"><input></form></div></form>
但是如果我们把这段代码在进行html解析。我们会发现DOM结构又发生了变化。
原因还是应为html规范,不允许form中存在任意形式的form子元素。
这种反复解析和序列化,最终得到的DOM结构是完全不一样的。但是这确实符合HTML规范的.
如何使用这种符合规范的DOM变异?
这时候需要我们知道一个html规范中定义的外部内容的概念(foreign content), ,外部内容一直是打破解析器和过滤器的一个神奇的工具。
首先我们要知道,html解析器创建dom树可以由3个命名空间的元素参与:
- html命名空间
- SVG命名空间
- MathML命名空间
默认情况下,html解析器工作在html命名空间中,但是当它解析到<svg>或者<math>标签的时候,就会对应的切换到那个命名空间中。
在这两个空间中都可以创建外部内容。在外部内容里的标签解析不同于原来的HTML标签。比如html命名空间中,<style>只能包含text,不能有子元素,
内部的html实体编码也不会被解码。但是在外部内容中,他就可以有子元素,对应的内容也可以被解码。
我们来思考下下面这片段代码的dom结构:
<style><a>ABC</style><svg><style><a>ABC
它的结构应该是这样的。遇到svg后,命名空间变换,后续元素不再是原来的html标签元素。元素表现发生了很大差异。
那么是不是所有svg和math标签包含的代码标签都不是html命名空间的呢。
不是的。html规范中有些元素被叫做MathML文本集成点和html集成点。这些元素的子元素都是html命名空间的。我的理解是解析器遇到这些点的标签时,会进行命名空间切换。
MathML的文本集成点:
- math mi
- math mo
- math mn
- math ms
HTML集成点:
- math annotation-xml
- svg foreignObject
- svg desc
- svg title
那么是否是所有Mathml文本集成店和html集成点的子元素都是HTML命名空间的呢??
不是。html规范又说了,大部分Mathml文本集成点的子元素都是HTML 命名空间的啊,但是除了<mglyph><malignmark>。当这两货直接是Mathml文本集成点的直接子元素的时候。他们不会切换命名空间。
那么如何确认一个元素当前的命名空间?大家自己好好想想~
后面回到最开始的
<form><math><mtext></form><form><mglyph><style></math><img src onerror=alert(1)>
第一次解析:
我们把这个段DOM结构进行序列化:
<form><math><mtext><form><mglyph><style></math><img src onerror=alert(1)></style></mglyph></form></mtext></math></form>
序列化后的html代码出现了嵌套form的情况。如果再次进行解析。那么DOM树:
关键在mglyph直接变成了mtext的子元素,导致style命名空间还是math,解析出了</math>。导致了xss的出现。
所以关键payload使用了两个form元素和mglyph元素。mglyph元素两次解析处于不同的命名空间是导致这次攻击的关键因素。
思考:
<math><mtext><table><mglyph><style><math><table id=”</table>”><img src onerror=alert(1)”>
老外说这段也可以绕过。可是我想不通。。。