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引用该元素

  1. <img src="" name="foo">

当插入的是name=”write”时,可以使用document.write获取,会替换原有的document.write

目标变量在之前没有被声明过

目标变量在之前没有被赋值过,情景如下

  1. dateStr = [day, month, year].join('-');
  2. let newInnerHtml = firstPElement.innerHTML + " | " + dateStr;

目标变量直接使用

DomClobbering的三种形式: window.SINK, window[‘SINK’], SINK

CodeQL的BUG
1, 认为sink是GlobalVarAccess,PASS,不算是BUG,这样是无法声明的,只能是存在这么一个全局变量

  1. function foo(){
  2. sink = [1,2,3];
  3. a = sink;
  4. }

2, 认为第二个sink不是GlobalVarAccess,PASS,不算BUG
can’t access lexical declaration ‘foo’ before initialization

  1. let sink = sink;

不可以在语句的左边

应用

破坏XSS过滤器
思路一:利用DOMClobbering在节点检测之前设置为该节点已经通过检测,从而绕过所有检测
思路二:利用DOMClobbering将节点设置为其他类型,例如注释,从来跳转到弱检测逻辑

案例分析

https://hackerone.com/reports/308158
项目下载地址
https://github.com/guardian/html-janitor/releases/tag/v2.0.2

漏洞演示

正常情况下

  1. var myJanitor = new HTMLJanitor({tags:{p:{}}});
  2. var cleanHtml = myJanitor.clean("<form><object onmouseover=alert(document.domain)></object></form>")
  3. console.log(cleanHtml);

源码分析

入口

  1. HTMLJanitor.prototype.clean = function (html) {
  2. var sandbox = document.createElement('div');
  3. sandbox.innerHTML = html;
  4. this._sanitize(sandbox);
  5. return sandbox.innerHTML;
  6. };

消毒

  1. HTMLJanitor.prototype._sanitize = function (parentNode) {
  2. var treeWalker = createTreeWalker(parentNode);
  3. var node = treeWalker.firstChild();
  4. if (!node) { return; }
  5. // 循环检测每一个node
  6. do {
  7. // Ignore nodes that have already been sanitized
  8. // 当当前节点的_sanitized不是flase,则跳过当前节点以及子节点的检测
  9. if (node._sanitized) {
  10. continue;
  11. }
  12. // 按顺序排查div得每个子节点
  13. // Check..
  14. if (isInvalid || shouldRejectNode(node, allowedAttrs)
  15. || (!this.config.keepNestedBlockElements && isNestedBlockElement)) {
  16. // Do not keep the inner text of SCRIPT/STYLE elements.
  17. if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {
  18. while (node.childNodes.length > 0) {
  19. // 将当前节点node的子节点插入到node之前,即div得孙节点变为子节点
  20. parentNode.insertBefore(node.childNodes[0], node);
  21. }
  22. }
  23. // node节点已经通过,然后移除
  24. parentNode.removeChild(node);
  25. this._sanitize(parentNode);
  26. break;
  27. }
  28. // Check.....
  29. node._sanitized=true;
  30. }while ((node = treeWalker.nextSibling()));
  31. };

案例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