CVE
https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=dom%20clobbering
HackerOne
https://hackerone.com/hacktivity?querystring=clobbering
https://portswigger.net/web-security/dom-based/dom-clobbering
https://research.securitum.com/xss-in-amp4email-dom-clobbering/
https://terjanq.medium.com/dom-clobbering-techniques-8443547ebe94
前提
当且仅当没有其他相同名称的变量的声明被提前先一步声明时
利用DOMClobbering破坏原有逻辑形成DOS攻击
https://hackerone.com/reports/1077136
利用DOMClobbering破坏防护程序,绕过校验
https://hackerone.com/reports/308158
YouTube DOMClobbering入门教程-视频
https://www.youtube.com/watch?v=5W-zGBKvLxk
在HTML使用img标签的属性name=”foo”,可以使用window.foo,document.foo,foo引用该元素
<img src="" name="foo">
当插入的是name=”write”时,可以使用document.write获取,会替换原有的document.write
目标变量在之前没有被声明过
目标变量在之前没有被赋值过,情景如下
dateStr = [day, month, year].join('-');
let newInnerHtml = firstPElement.innerHTML + " | " + dateStr;
目标变量直接使用
DomClobbering的三种形式: window.SINK, window[‘SINK’], SINK
CodeQL的BUG
1, 认为sink是GlobalVarAccess,PASS,不算是BUG,这样是无法声明的,只能是存在这么一个全局变量
function foo(){
sink = [1,2,3];
a = sink;
}
2, 认为第二个sink不是GlobalVarAccess,PASS,不算BUG
can’t access lexical declaration ‘foo’ before initialization
let sink = sink;
不可以在语句的左边
应用
破坏XSS过滤器
思路一:利用DOMClobbering在节点检测之前设置为该节点已经通过检测,从而绕过所有检测
思路二:利用DOMClobbering将节点设置为其他类型,例如注释,从来跳转到弱检测逻辑
案例分析
https://hackerone.com/reports/308158
项目下载地址
https://github.com/guardian/html-janitor/releases/tag/v2.0.2
漏洞演示
正常情况下
var myJanitor = new HTMLJanitor({tags:{p:{}}});
var cleanHtml = myJanitor.clean("<form><object onmouseover=alert(document.domain)></object></form>")
console.log(cleanHtml);
源码分析
入口
HTMLJanitor.prototype.clean = function (html) {
var sandbox = document.createElement('div');
sandbox.innerHTML = html;
this._sanitize(sandbox);
return sandbox.innerHTML;
};
消毒
HTMLJanitor.prototype._sanitize = function (parentNode) {
var treeWalker = createTreeWalker(parentNode);
var node = treeWalker.firstChild();
if (!node) { return; }
// 循环检测每一个node
do {
// Ignore nodes that have already been sanitized
// 当当前节点的_sanitized不是flase,则跳过当前节点以及子节点的检测
if (node._sanitized) {
continue;
}
// 按顺序排查div得每个子节点
// Check..
if (isInvalid || shouldRejectNode(node, allowedAttrs)
|| (!this.config.keepNestedBlockElements && isNestedBlockElement)) {
// Do not keep the inner text of SCRIPT/STYLE elements.
if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {
while (node.childNodes.length > 0) {
// 将当前节点node的子节点插入到node之前,即div得孙节点变为子节点
parentNode.insertBefore(node.childNodes[0], node);
}
}
// node节点已经通过,然后移除
parentNode.removeChild(node);
this._sanitize(parentNode);
break;
}
// Check.....
node._sanitized=true;
}while ((node = treeWalker.nextSibling()));
};
案例2
Dark-lang
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22540
https://github.com/dart-lang/sdk/releases/tag/2.12.0-0.0.dev