随着诸如XSS和CSRF之类的经典客户端漏洞被修补,CSP和SameSite被淘汰,诸如DOM Clobbering之类的利基攻击技术变得越来越重要。 MichałBentkowski最近使用DOM Clobbering来利用GMail,六年后我在2013年首次引入该技术。 在这篇文章中,我将快速介绍DOM Clobbering,使用一些新技术扩展我的原始研究,并分享两个交互式实验室,以便您可以亲自尝试这些技术。 如果您还不熟悉DOM Clobbering,则可能需要先阅读Web安全学会中的DOM Clobbering简介。
确定DOM元素关系
首先,获得可以一起使用的HTML元素列表非常简单。 您只需要将两个HTML元素彼此相邻放置,分别为其分配一个ID,然后检查第一个元素是否具有第二个元素的属性。
这是生成HTML关系的代码:
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破坏技术
通过使用具有某种形式的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>
您可能想知道为什么不只使用属性。只有在HTML规范将其定义为有效属性的情况下,它们才起作用。 这意味着任何尚未定义的属性都不会具有DOM属性,因此未定义。 例如:
<form id=x y=123></form>
<script>
alert(x.y)//undefined
</script>
您可以在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 {
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'));
运行上述所有代码时,我在结果“用户名”和“密码”中注意到两个空白字符串。 这些是锚标记的DOM属性,而不是HTML属性。 看来您可以通过锚点控制这些值。 通过反复试验,我发现这些属性与锚点URL的用户名和密码部分有关,该锚点通常与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对值进行编码。 可以使用不同的协议来解决这个问题,例如文件系统URL或其他协议:
<a id=x href="abc:<>">
<script>
alert(x)//abc:<>
</script>
Firefox还允许您在基本标记中使用其他协议,并且该协议将由锚点使用并允许未编码的值:
<base href=a:abc><a id=x href="Firefox<>">
<script>
alert(x)//Firefox<>
</script>
可以在Chrome中执行相同的操作,但这一次在基本href中提供所需的值:
<base href="a://Clobbered<>"><a id=x name=x><a id=x name=xyz href=123>
<script>
alert(x.xyz)//a://Clobbered<>
</script>
我们已经在网络安全学会中发布了两个围绕此技术构建的交互式DOM实验,因此您可以自己尝试一下:
更新...正在超过3个级别
因此,@ Terjanq提到可以使用iframe和srcdoc破坏对象的多个级别的属性。 该技术之所以有效,是因为在iframe上使用name属性时,iframe的实际contentWindow会分配给全局变量。 然后,您可以将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。 但是,我找到了使用iframe避免超时的方法! 如果您有导入样式表的样式/链接元素,则会创建一个小的延迟,从而使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>