这里只引入两篇文章
(中文版翻译)[https://www.anquanke.com/post/id/219089]
(英文原版)[https://research.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass/]
(good)[https://jorianwoltjer.com/blog/p/research/mutation-xss]
不建议看其他文章
前言
xss乃大势所趋,同样是一个很好的对web手的考察,总不能还不会xss吧,但是总是学那一套也学不会什么东西,学一点新又不新的东西,突变xss
对于xss的学习,这里插一嘴,为什么比赛这么多啊,多到赛后都不想复现了()
DOMPurity
正式学习突变xss前,先了解一下什么是DOMPurity?
它是一款用于过滤html防范xss的库,简单来讲,你传入一段html,它会把它解析为DOM树,然后DOMPurity会遍历DOM树中的所有元素以及属性,删除不在白名单的所有节点,接着再序列回html,然后赋值给innerHtml,被浏览器解析
1 | div.innerHTML = DOMPurify.sanitize(htmlMarkup) |
这种序列化、重新解析的往复操作并不一定能返回原始的DOM树(比如会”善良”的补足标签),因此有可能产生突变型xss
突变xss
在正式学习之前,我们得了解几个所谓的边角特性,看看他们到底有什么魔力
嵌套form元素
要知道,HTML标准中规定,不允许在一个表单元素(form元素)中嵌套另一个表单元素。
如果发现存在嵌套,解析器会尝试”修正”结构,最终第二个 form 标签在 DOM 中不被当作独立子表单创建。这样会影响原本以其为父的子节点切换父节点,这种解析之间,就会发生类似雪崩的一系列变化
MathML命名空间
这边都不会详细介绍,只是引入这个概念()
payload
DOMA
1 | <form><math><mtext></form><form><mglyph><style></math><img src onerror=alert(1)> |
第一次,服务器会如何解析呢?
从左到右 token 化并处理
先进入form,建立了一个form₁(HTML 命名空间),并form pointer = form₁(记录当前打开的 form)
然后进入math,进入 MathML 命名空间,创建 math(MathML)
然后进入mtext,(MathML 文本集成点)。
注意:mtext 的子节点通常默认被解析为 HTML 命名空间(除非子节点是某些特殊元素触发例外)。
然后闭合form(即把 form pointer 设为 null),在此之后再创建form是允许的
因为 form pointer 刚被设为 null,可以创建一个新的 form₂ —— 而这个 form₂ 此刻处在 mtext(MathML)内部的位置
因此它被插入为 mtext 的子节点。但由于它的父是一个 HTML 表单节点(form₂ 是 HTML 元素),因此 form₂ 与其所有子节点都属于 HTML 命名空间(在第一次解析的上下文中)。
因此 mglyph 在第一次解析后 DOM A 中处于 HTML 命名空间
在 HTML 命名空间的 style 元素内,随后的 img src onerror=… 只是style 的文本内容(在 HTML 的 style 标签里不再解析成元素),因此目前 DOM A 中并没有真正的 img 元素节点,只有一段文本。
1 | form₁ (HTML) |
DOMpurify返回的字符串
1 | <form><math><mtext><form><mglyph><style></math><img src onerror=alert(1)></style></mglyph></form></mtext></math></form> |
包含嵌套的 form 标签
至于为什么这样是因为
“第一次解析”时,由于 浏览器实际上已经把两个 form 变成嵌套关系了。
关键在 HTML 解析算法是基于栈的,不会“跳回去”找兄弟节点。
浏览器只会在「当前 open element」(这里是 mtext)下放置新标签。
你看到的源码里,虽然肉眼觉得两个 form 是平级的,但解析器不会退栈到外层 form,因为前面有 math mtext 没关
要理解一个核心概念
因为在遇到 </form>
时,浏览器只是把 form pointer 清空,并没有回退 DOM 栈
于是第二个 form 自然就成了 <mtext>
的子节点。
DOMB
遇见嵌套情况,导致第二个 form 的 start-tag 在此处 被忽略(或不创建新的 form 元素),于是 mglyph 不再有 form₂ 作为父,而直接成为 mtext 的子节点——这一步把 mglyph 的命名空间从 HTML 变成 MathML(因为 mtext 的直接子元素按规范会进入 MathML,mglyph 在这种位置是 MathML)
HTML 规范中明确:当 mglyph 是 mtext 的直接子节点时,它属于 MathML 命名空间(这是对 mglyph 的特殊例外)。
所以现在 mglyph 的 namespace 由 HTML → MathML。
接着 style 在 MathML/foreign-content 上下文中,其内部的文本不再被视为“纯文本”,而会按 foreign-content 的规则被重新解析(在 MathML integration/text integration 的特定规则下,style 的内容可以被解释成子节点或被实体解析)。
因此序列化时候作为文本的 <img src onerror=...>
在第二次解析过程中被解析成真正的元素(最终会进入 HTML 命名空间或被插回到文档树),从而生成真正的 <img onerror=...>
节点并执行
1 | <mtext> (MathML) |
也许你会想,既然<style>
变换了命名空间,那么<img>
呢?<img>
属于 HTML 的“break out 标签”;
解析器看到它时,会立即退出 MathML 命名空间,在 HTML 命名空间里重新创建一个 <img>
元素
有点逃逸的意思在吧,二次解析
结语
有点囫囵吞枣了,只是简单的通了一下,原文内容不少,可以看看,Mutation XSS 还是比较复杂的,需要深入理解才能掌握,也许以后会改第二版内容()
这里引出第二种内容
1 | <form><math><mtext></form><form><mglyph><svg><mtext><title><path is="</title><img src onerror=alert(origin)>"> |