DOM破坏及复现实验

DOM与window的量子纠缠

举一个例子,给button的标签id设为btn,然后在script标签中使用window.btn来抓取id=btn的值。通过结果可以看到,window.btn是将整个标签全部都抓取了出来。
在这里插入图片描述
在这里插入图片描述
这个也就是DOM和window的量子纠缠,也就是说:在HTML中设定了一个有id的元素在之后,可以在js中就直接操作,并且由于js的作用域的规则,这也可以直接使用btn,因为如果在当前的作用域找不到的情况下,他会一直往上去寻转,直到找到为止。
并且在HTML的说明文档中有明确说明中有提到节选的重点:
在这里插入图片描述
选中的位置就是所提及的重点;总结来说就是**除了id可以直接使用window存取到以外;embed、form、img和object这四个标签也可以使用name进行操作**也就是说可以使用HTML的元素来影响js。

DOM Clobbering(DOM破坏)

刚刚所说的利用HTML元素来影响js这个就是可以被理解为DOM破坏。也就是通过DOM来把一些东西给覆盖掉的方法。
下面这个代码就是:如果你存在test1和test2这两个元素,就执行这俩个元素。但是这次是两个元素,不是一个了。

if (window.test1.test2){
	eval(''+window.test1.test2)

在这之前存在两个问题:

1.利用html标签的属性id,很容易在window对象上面创建任意的属性,但是我们能在新对象上创建新属性么?也就是说不在window下创建,而是在form标签,p标签等去创建。
2.怎样控制DOM elements被强制转为string之后的值,大多数的dom节点被转为string后是[object HTMLInputElement]。

首先,在js中,每一个函数几乎有两个方法,分别时valueof方法和tostring方法
valueof方法是将返回报装对象实例对应的原始类型的值
tostring方法返回的是对应的字符串的形式
对象的valueof方法总是返回对象自身,这种时候在调用对象的tostring方法,将它转换为字符串,比如说:
在这里插入图片描述
对象的tostring默认返回的方式是[object,Object].
但是我们在测试的时候可以看到:
我们在form表单中创建id的属性值是test1,然后在input标签中创建name属性值是test2.使用alert进行
在这里插入图片描述
这样的话它的运行结果是会弹窗的,但是返回的并不是两个object,而是这样的,像一个字符串,但是我们是需要使用eval执行的,执行这样的数组就是没有意义的。而需要执行的是一个函数。
在这里插入图片描述
针对于上面提到过的第二个问题,有下面这样的代码,它可以遍历html中所有可能的元素并且检查它们的tostring方法是否继承自object.prototype或是以另一种方式定义。

getOwnerPropertyNames:遍历object在window下的所有属性。

在这里插入图片描述
可以看到它第一个返回的是area,另一个是a标签。在a标签中tostring方法只返回一个href属性值。所以刚刚前面举的例子的解决方案就是,设我们有两个相同的id的值。分别是:

<a id =test1>click!</a>
<a id =test1>click2</a>

因为给的id的值都是相同的,所以在给出的结果的值是HTMLCollection,并且组成了一个类似于数组的样子。
在这里插入图片描述
也就是说。我们可以通过index以及访问其中特定元素的id,也就是说window.test1.test1实际上是指第一个元素。同时,设置name属性也可以在htmlCollection中。
也就是说在刚刚的标签中在加一个name的属性。

<a id =test1>click!</a>
<a id =test1 name=test2>click2</a>

它的结果就会把元素所以在的标签给取出来。
在这里插入图片描述

基于以上复现实验

第一个:单个循环,移除标签的属性。

下面代码是:首先截取#号后面的值,然后创建一个div,然后将#号后面的值都赋值给div,然后使用querySelectorAll选取div下所有的子元素;然后获取子元素的属性,并将属性全部删除,但是我们在运行的时候会发现一个问题。

<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>

我们明明输入的是:<img src=1 οnerrοr=alert(1)>,但是运行后得出的结果是它只删除了一个src,把onerror留了下来。
在这里插入图片描述
这里使用调试方法来查看它每一步运行的值。
我们现将断点放在 for (let el of root.querySelectorAll('*')) {for (let attr of el.attributes) {这两行上,然后逐步往下走,可以在右边的作用域中看到它的data是:<img src=1 onerroe=alert(1)>
获取到了这个,
在这里插入图片描述
然后我们就接着往下走。然后可以看到el获取的值是img,也就说说它已经成功获得到了这个标签。
在这里插入图片描述
然后接着继续走;然后attr获取到了第一个元素:src,之后,它执行了下面的移除操作。
在这里插入图片描述
接下来按照预期应该是回到标签内继续匹配元素onerror然后进行删除,但是当继续下一步的时候,attr取到的值是空的,就直接跳出循环,直接结束。
在这里插入图片描述
按照我们的思维这中方式应该是正确的,但是,在进行循环的过程中,attr首先匹配到的是src元素,然后在循环过后直接删除,删除了之后,剩余的哪个onerror自动往前移动,就好像做核酸一样,拍你前面的那个人因为没有带口罩被撵走了,那你自然是要往前走一位来替代它的位置。这个也是,onerror替代了src排的第一个的位置,它就变成了第一个,但是我在刚刚循环的时候,已经把第一个给循环了,我要去循环下一个的时候它没有了,所以我就结束循环了。
所以要破解这关的话就是将元素的顺序打乱,根据他的漏洞来让他删除后所剩下的是我们所需要的就可以了。
就是将src元素写道第2为,在删除第一位的时候它变成第一位就会保留了,然后将onerror保存到第四位,那样在删除第一位后,原来的第三位变成了第二位,第四位就变成了第三位,,第二位运行完后被删除,最开使的第四位就变成了第2位,但是我们已经执行完第二位了,所以后面就没有了,他就跳出了循环。
在这里插入图片描述
总结来说:
在这里插入图片描述

第二个:两个循环,移除标签的属性

由于刚刚那个代码是存在漏洞的,所以进行了修改,分成了两个步骤,第一个循环进行存放,第二个循环进行删除,解决了刚刚的问题。

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); 

这个循环也确实会将所有的元素给过滤掉。
可以看到输入的<img src=1 οnerrοr=alert(1)>里面的元素都没了,只剩了一个img标签。
在这里插入图片描述
解决这个问题,我们可以从两个方向来进行攻略

1.进入循环但是让他删除的是没有用的数据

这种方式就可以使用到刚开始讲的那个DOM破坏的方式来进行。
el执行的是attr,如果有一个元素可以劫持这个,那么删除的就不是atr而是里面的一个子元素。
现测试一个例子:

<body>
 <form id="x"action="">
        <img name="attributes">
 </form>
</body>
<script>
	console.log(window.x.attributes)
</script>

执行上面代码的时候,的出来的结果是可以打印出来整个标签的。
在这里插入图片描述
也就是说,这里的x是上面的el;插入一个form之后,这个el就相当于是等于这个form的,而那个el.attributes相当于是哪个img。也就是让img进入循环,而在form中进行触发,这样就实现了进入循环,但是删除的是无用的标签。
所以可以使用刚才的方法测试:

http://localhost//demo4/demo4.html#%3Cform%20action=%22%22%3E%3Cimg%20id=attributes%3E%3C/form%3E
但是它的结果显示是:el.attributes不是一个可迭代器,
在这里插入图片描述
可迭代对象有一个特征就是for循环,现在进入的只有一个元素,他是循环不了的,所以我们需要将他组成数组或者集合,
而刚刚我们刚刚正好说了,如过id的值是相同的话会组成一个集合,而这个就刚好满足了刚刚咱们所需要的。所以它就可以写成下面的形式:

<form id="x"action="">
        <img name="attributes">
        <img name="attributes">
 </form>

这样的话,img标签就进入了循环删除,这样的话我们form里面还缺少一个触发的属性,而onfocus属性正好可以自动触发,但是它不是form属性,而是input下面。我们也可以将img换成input,这样也可以满足name相同的时候会变成一个集合。
这个解决之后还需要自动聚焦,这个时候就需要一个自动聚焦的属性。

tabindex:全局属性,以及它是否(在何处)参与顺序键盘导航。

加上tabindex属性的话就可以把焦点聚集在input上,否则onfoucus是没有办法实现的。
所以这样的话:我们就可以进行尝试:

<form tabindex=1 onfoucus=“alert(1)” autofocus=“true”><input name=attributes><input name=attributes></from>

这种是成功跳出弹窗的,但是因为这个是自动将你的鼠标自动对焦,所以会一直进行弹窗,所以我们可以在它执行成功一次之后将他移除。

<form%20tabindex=1%20οnfοcus="alert(1);this.removeAttribute(‘onfocus’);"autofocus=“true”><input%20name=attributes><input%20name=attributes></form>

在这里插入图片描述

2.不进入循环

这里使用的是两个svg标签,也就是使用\<svg>\<svg onload=alert(1)>来尽行绕过,它可以在过滤代码之前进行绕过,也就是说,它在代码的root.innerHTML = data;就已经执行了。

在这里插入图片描述
要解释这个的话首先要了解以下浏览器的渲染过程。
也就是在DOM树构建完成之后,会触发DOMContentLoaded事件,接着就会加载脚本或者图片,然后执行全部加载完成后会触发load事件。
使用img标签失败的原因是:当我们使用断点测试的时候,首先先将过滤给注释掉,然后我们将断点放在root.innerHTML = data;上,然后进行一步一步的测试:
第一步:往下走的第一步的时候它会直接走到最后,但是img标签并没有执行。
在这里插入图片描述
在往下走的时候才会执行img并且可以弹窗
在这里插入图片描述
也就是说,如果加上过滤的话,它是先循环过滤了才可以进行弹窗,但是经过过滤后子元素就被过滤掉了。也就是说js阻塞了DOM树的构建;也可以说在script标签内的JS执行完毕以后,DOM树才会构建完成,
而svg执行的时候就是会先执行;和img测试的方式相同,在同样的位置进行断点测试。
在 root.innerHTML = data;位置的时候,往下走可以看见它会直接进入到alert()然后弹窗。
点击下一步的时候,它会直接进入到alert并且进行弹窗。
在这里插入图片描述
这样的话,它没有进入到循环删除就已经可以进行弹窗。接着往下走的话的话即便被删它也已经执行过了,所以也没有必要了。
也就是说,这种嵌套的svg成功的原因是因为当页面为root.innerHtml赋值的时候浏览器进入DOM树构建过程;在这个过程中会触发非最外层svg标签的load事件,最终成功执行代码。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值