目录
项目源码:
<script>
const data = decodeURIComponent(location.hash.substr(1));;
const root = document.createElement('div');
root.innerHTML = data;
// 这里模拟了XSS过滤的过程,方法是移除所有属性,sanitizer
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>
题目分析:
可以看到这是个明显的DOM XSS,用户的输入会构成一个新div元素的子结点,但在插入body之前会被移除所有的属性(例如:<img src=x οnerrοr=alert(1)> 经过上述代码过滤后会变为<img>,使得我们传入的数据失效)
解法:绕过过滤-DOM clobbering
DOM clobbering主要是用来进行DOM型的XSS攻击,其可以篡改JS函数原本的属性恶意插入一些XSS代码到页面的JS中去,它的特征就是利用了元素配置id或name属性后可以使用包括document、window、自己名称的形式进行访问(假如我们写了一个a标签,给他了一个id和name,那么我们就可以通过id或name来拿到这个a标签)。其可以对document的属性进行恶意的替换。特别是在元素的属性名与JavaScript全局对象的属性名冲突时,可能会破坏正常的DOM操作或脚本运行从而影响dom树的结构造成破坏。也是这一特性让它有了DOM COLBBERING这个名号。
DOM clobbering原理
全局变量自动创建
当DOM元素拥有id或name属性时,浏览器会自动在全局作用域(即window对象上)创建一个同名的属性,指向该DOM元素。例如,<div id="foo">会创建一个window.foo属性,指向这个div元素。
属性名冲突
如果元素的id或name属性与已有的JavaScript全局对象或内置属性名冲突,会覆盖原有的JS代码。例如,如果有一个<input id="location">,它会覆盖window.location,这会导致脚本中试图访问window.location时得到<input>元素,而不是期望的Location对象。
影响脚本执行
由于全局变量被覆盖,脚本在访问这些全局变量时会得到意外的结果,导致错误或不可预测的行为。
示例一:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img id = "x";>
<img name = "y";>
</body>
<script>
console.log(x);
console.log(y);
console.log(document.x);
console.log(document.y);
console.log(window.x);
console.log(window.y);
</script>
</html>
可以从控制台看出:
id和name中的值可以直接引用,引用的值即为标签的全部,但使用document可进行name引用,不可进行id引用。 DOM元素<img>拥有id属性=“x”,浏览器会自动在document对象上创建一个同名的属性document.x指向该DOM元素<img>,因此使用document.x可以直接抓取<img>标签,无需通过 getElementById 抓取<img>标签。
示例二:
我们在控制台输入上述代码,结果却是输出为空
而当我们新创建的<div>标签中插入一个<img>标签并将该<div>标签插入到<body>中后,再利用document.cookie函数输出网页的cookie时,输出的结果却由空值改变成<img>标签
造成这一现象的原因:
<img>标签拥有name属性=“cookie”,浏览器会自动在document对象上创建一个同名的属性document.cookie指向该DOM元素<img>
浏览器创建的同名name=cookie属性与已有的JavaScript内置属性名cookie冲突,会覆盖原有的JS代码的作用即执行document.cookie时不是获取当前网页的cookie值而是抓取<img>标签
这种行为虽然有时可以方便地访问元素,但也会引发一些潜在的问题,特别是在元素的属性名与JavaScript全局对象的属性名冲突时,可能会破坏正常的DOM操作或脚本运行。我们可以利用这种方法。
下段调试:
<script>
const data = decodeURIComponent(location.hash.substr(1));;
const root = document.createElement('div');
root.innerHTML = data;
// 这里模拟了XSS过滤的过程,方法是移除所有属性,sanitizer
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>
payload:
<style>@keyframes x{}</style><form style="animation-name: x" onanimationstart="alert(1)"><input id=attributes><input id=attributes>
当程序走进第一个style标签中,因为没有属性,所以不会进行过滤
当走进form标签中时,因为form中还嵌套了两个input标签,而他们的id刚好为attributes,所以程序中的el.attributes刚好把两个input标签拿到了,变成一个数组[0:input#attributes,1:intup#attributes]
然后attr拿出了数组中的第一个元素:input#attributes ,但此时input#attributes中的name为" ",所以下面的代码就不会移除name
然后再拿出数组中第二个元素,name也为空
当循环完成后,程序又走进input标签中,此时input的name会被程序过滤掉
最终结果为:
ok,弹窗成功,完成。