源码:
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>Documenttitle>
head>
<body>
body>
<script>
const data = decodeURIComponent(location.hash.substring(1));
const root = document.createElement('div');
root.innerHTML = data;
//这里模拟了xss过滤的过程,方法是移除所有属性
for(let el of root.querySelectorAll('*')) {
for (let attr of el.attributes) {
el.removeAttribute(attr.name);
}
}
document.body.appendChild(root);
script>
html>
因为现在是没有任何属性的,所以输出aaa

输入这样的标签确实没有任何属性,但是源码中是innerHTML,所以只能用img标签

输入,发现把src属性删掉了

我们去断点调试一下,看看究竟为什么只删除了src属性,没有删除onerror属性
1、首先el是没有值的

2、el有值了,它是img,因为img传进来了

关键的是去看img中的attributes,有两个属性,一个src,一个onerror,看上去像数组一样的一个东西,有索引0和索引1。

3、继续走,attr把第一个选中了,选中了src属性

下面是remove,把src属性移除掉,onerror属性向前移一位,然后索引因为没有下一位了,所以循环结束,onerror属性也就留了下来
基于这个点,我们怎么去绕过呢?调整顺序,被过滤掉13属性,留下了24属性
输入


成功绕过!
这种做法还有很多:
这是循环删除的方式,那现在改变它,不用边循环边删除
源码:(这次先把属性放在数组中,然后再数组中循环删除)
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>Documenttitle>
head>
<body>
body>
<script>
const data = decodeURIComponent(location.hash.substring(1));
const root = document.createElement('div');
root.innerHTML = data;
//这里模拟了xss过滤的过程,方法是移除所有属性
for(let el of root.querySelectorAll('*')) {
let attrs = [];
for (let attr of el.attributes) {
attrs.push(attr.name);
}
for (let name of attrs) {
le.removeAttribute(name);
}
}
document.body.appendChild(root);
script>
html>
这次再测试发现无效了

img标签的属性也被删除了

接下来有两种方式来解决
方法一:要么别进循环直接执行
方法二:要么进循环删除无用元素
dom cobbing
一个svg一个DOM
但svg刚刚试了不行,把onload属性删掉了
如果有一个元素可以劫持el.attributes,那也许删除的就不是el,而是el的一个子元素
<body>
<form id="">
<img name="attributes">img>
form>
body>
<script>
console.log(window.x.attributes)
script>
我们插入一个form,那el此时就是form,el.attributes是谁?是form里的img
那我们让img进循环把img属性删掉,然后我们的form触发弹窗就OK了
输入

事实上img已经进来了,但是不是可迭代对象,所以for循环无法进行,一个元素肯定不是可迭代对象,因此我们需要把它组成一个集合或者数组。
两个标签即可搞定
一直锁定聚焦:
<form tabindex=1 onfocus="alert(1)" autofocus="true"><input name=attributes><input name=attributes>form>
改进:聚焦生效一次后删除:
成功绕过!
通过debug分析:form确确实实走进来了,但是走进来归走进来,它删除的是后面的两个input,和我前面的触发语句没有什么关系,为什么?因为你仅仅是删除了input的属性,并没有把input的标签删除,这样我的tabindex还是聚焦在这里。
虽然你升级了,但是我们依然通过dom破坏的方式绕过了
输入

两个SVG成功了,分析---->为什么?
我们把过滤注释掉,去断点分析:

JS走了一步走到底部了

再走一步,走到img了,显示打印alert(1)但还没弹窗,很明显js把dom树阻塞了,js执行完毕,img才开始执行,意味着img标签逃不过js的过滤。
alert(1)是在页面上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>Documenttitle>
head>
<body>
body>
<script>
window.addEventListener("DOMContentLoaded", (event) => {
console.log('DOMContentLoaded')
});
window.addEventListener("load", (event) => {
console.log('load')
});
const data = decodeURIComponent(location.hash.substring(1));
const root = document.createElement('div');
root.innerHTML = data;
script>
html>
可见当我们的JS执行完毕之后DOMContentLoaded(DOM的内容被加载进来),加载进来以后才执行img的属性
JS先执行,img后执行

1、js先执行,js执行完毕,DOM树构建完成
2、触发DOMContentLoaded事件
3、加载脚本、图片(img,css)等外部标签
4、触发load事件
结论:js可以阻塞DOM树的构建,导致js先执行,img后执行
两个svg degbug调试看执行的先后顺序
第一步

第二步

第三步

原理:如果是一个svg,则直接执行到这里就return返回了,因为它是最外层
返回之后继续往下走,在script标签执行完毕,再执行第二个

而一旦最外层svg加载了一个子元素svg,则直接加载了一个onload事件执行了
最开始判断的是最外层的svg,最外层的是return,如果不是最外层则走的是下面那个方法sendsvGLoadEventIfPossible();

判断:如果你是svg元素,并且你有一个相应的onload事件,则创建一个load事件(触发代码直接执行)

只要你前面创建了svg元素,并且对load事件编写了相关的代码,满足这俩个条件你就可以进入这个if中,就可以直接加载你的onload事件
如果你是最外层的话直接退出往下走,如果你不是最外层,它在innerHTML那里直接加载onload事件直接在那执行!
测试代码:
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>Documenttitle>
head>
<body>
body>
<script>
window.addEventListener("DOMContentLoaded", (event) => {
console.log('DOMContentLoaded')
});
window.addEventListener("load", (event) => {
console.log('load')
});
const data = decodeURIComponent(location.hash.substring(1));
const root = document.createElement('div');
root.innerHTML = data;
script>
html>
URL输入的值:
console回馈如下

可以很明显的看到,最外层的是svg0,除了外层,里面的是svg2和svg1
很明显的看到在DOM没加载之前svg2和svg1就执行了,而最外层的svg0执行在DOM加载完之后。
那么就很好解释为什么俩个svg触发了,而一个svg没有触发了。
内层的svg在DOM构建之前就执行了,而最外层的svg在DOM树构建完成之后才执行。