目录
- 一个循环循环题目:要实现弹窗
- 一、代码
- 二、代码分析
- 三、试运行代码
- 四、寻找原因
- 1、断点查询
- 2、简化测试
- 五、解决问题
- 1.进循环删除无用数据
- 2.不进循环
- 两个循环题目:要实现弹窗
- 一、代码
- 二、代码分析
- 二、试运行代码
- 四、解决问题
- 1、进循环删除无用的属性
- 2、不进入循环
一个循环循环题目:要实现弹窗
一、代码
<!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>
// http://127.0.0.1/domfilter/#<img src=1 οnerrοr=alert(1)>
const data = decodeURIComponent(location.hash.substr(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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
二、代码分析
截取#后面的值
const data = decodeURIComponent((1));
- 1
创建一个div
const root = ('div');
- 1
将data数据用innerHTML赋值给root【innerHTML不执行script】
= data;
- 1
这root同div,而querySelectorAll选取子元素其中*表示所有的被选中。插入到el中
let el of ('*')
- 1
获取子元素属性
let attr of
- 1
将子元素属性全部删除
();
- 1
三、试运行代码
试输入666
因为666就是个字符串没有属性所以没有过滤。
为此使用其他方法来验证过滤
因为innerHTML不执行script,同时为了直观表现
所以运用img来弹窗。如果全部过滤则不会弹窗,没有过滤成功则会弹窗。
#<img src=1 onerror=alert(1)>
- 1
输入它并没有弹窗,为此我们进行查看输入内容的属性是否全部过滤了。
上图显示很奇怪的是它过滤属性去掉了src,可是onerror缺没有被去掉。
四、寻找原因
1、断点查询
对于代码的运行过程查看通常我们使用断点查询。
好处在与无需修改代码便可查看断点内的过程
断点查看
设置断点在删除属性那
查看初始数据
最初我们发现data最初为
data: "<img src=1 onerror=alert(1)>"
开始当el识别到了img
我们查看到此时img有src与onerror两个属性
开始第一个循环
如图此时它识别到了src,并且有name属性为此removeAttribute将其删除。
后续持续不断下一步发现它并没有对onerror属性进行识别删除,而是直接跳出了循环
2、简化测试
基于本实验运行过程我们知道,它是在一个一个的遍历查询进行删除。只要满足了条件就将其内容在组中删除。
于是我们简化操作:
a = [1,3,4,2,5,6]
for i in a:
print(max(a),end=',')
a.remove(max(a))
- 1
- 2
- 3
- 4
- 5
结果:显示只有6、5、4。
为此我们同样断点调试
最初:
开始调试:
边遍历边删除演示
分析结果:属性的逃逸
结果显示为6、5、4。本应该输出1、3
1、首先我们给予的是一个数组,为此在遍历时是一个一个进行。当前判断完就跳下一个,即:例如从1开始,第1个数鉴定完后就到第2个数进行判断
.
2、明白了判断流程如此就可以说明。当第一次判断从第一位数字1开始,然后删除了max值6.随后第二次判断进入下一位从第二位数字3开始,删除了max值5。再第三次判断从第二位数字4开始删max值4。此时数组剩余1,3,2了,没有第四位了于是就结束了。
.
3、每一次删除数字后的数字都会往前补一位于是就导致有的数是没有被遍历到的。
五、解决问题
1.进循环删除无用数据
进循环删除无用数据其实就是是找了个替罪羊一样提前运行判断。然后真正的代码正常执行。
简单的说就是: 录入无用元素,删除无用元素
#<img a=bbb src=1 c=sdd onerror=alert(1)>
- 1
成功让恶意代码进入循环,让他们后一位的真正代码逃过了进入循环。
另使用details标签一样可以,它是open被过滤了后面的οntοggle=alert(1)替代上了open
<details open ontoggle=alert(1)>
- 1
details 有延迟的话 肯定执行成功,因为此时异步事件已经执行完成,执行点在innerhtml
如果没有延迟,有可能在js删除属性之后,异步事件才执行完成
2.不进循环
这个方法利用两次<svg>
对于我们输入的内容会被js执行一次最内部的阻碍了建立DOM树,在第一次执行非最外层svg标签的load
事件的时候这个事件就如同一个幌子,为了迷惑程序它已经把我们过滤过了。DOM树建立完后管不了我们了,此时我们真正代码没有进入循环没有被过滤就运行直接触发。
我们的目标是绕过sanitizer来执行XSS。大概的过滤过程是:
1.先正则直接去除注释与onload属性的内容
⒉将上面处理后的内容,赋值给一个新创建的div的innerHTML属性,建立起一颗DOM树
3.用黑名单删除掉一些危险DOM节点,比如等
4.用白名单对属性进行—遍处理,处理逻辑是 。只保留白名单里名字开头的属性 。对于满足正则/href |src / background/i的属性,进行额外处理
5.处理完成后的DOM,获取其HTML代码返回
扩展点:
js标签执行了再执行html。因为先执行所以可以阻碍dom树生成
js执行 =》dom树阻塞 =》dom加载完后 =》load事件触发 =》结束
#<svg><svg 20onload=alert(1)>
- 1
套嵌的svg之所以成功,是因为当页面为赋值的时候浏览器进入DOM树构建过程;在这个过程中会触发非最外层svg标签的
load
事件,最终成功执行代码。所以,sanitizer执行的时间点在这之后,无法影响我们的payload。
两个循环题目:要实现弹窗
一、代码
<!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;
// 这里模拟了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>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
二、代码分析
这个代码与第一代码目的相同,不同的是它是第一个的优化。优化在于它利用了另一个组来存储,避免了恶意属性的逃逸。即一个组来存储数据,另一个来删除数。
基础代码同第一个题目,我分析一下不同之处的代码帮助大家打开思路:
这root同div,而querySelectorAll选取子元素其中*表示所有的被选中。插入到el中
let el of ('*')
- 1
创立attrs数组
let attrs = [];
- 1
获取上面el即所有子元素的属性
let attr of
- 1
把name属性的值放到attrs数组中
();
- 1
获取attrs数组的name值给name用于删除
let name of attrs
- 1
二、试运行代码
于上题目一样,使用img来看过滤情况
#<img%20src=1%20οnerrοr=alert(1)>
- 1
不难发现它确实比一个循环的题目优化了,他把所有的属性的过滤了!
四、解决问题
1、进循环删除无用的属性
本题的关键在于把let attr of
这一步,既然我们要进入循环那么便将被过滤条件判断是否被过滤。而这一步代码的判断决定了那些会被推入到attrs数组中。为此我们要劫取到这一步的数据并修改推入到attrs数组的数据。
为此提及这点就要运用dom与window的量子纠缠的知识,并需要一个可迭代对象(组)。
可迭代对象组
可迭代对象需要在同一集合中或有for循环
解释解题思路
1、这个题目会过滤掉属性是没错,但是我们运用dom与window的量子纠缠的知识可以过对父标签的过滤。即用window向上寻找属性获取标签。而其中有一个标签关系如上图,但是对于运用举例说明
例如:
<body>
<form id="a" action="">
<img id="a" name="c" />
</form>
<script>
console.log(window.a.c)
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
结果网页输出,在控制台是直接读取了form下的img标签
上面例如涉及到当id或者name相同时,他规为同一个集合。当归为一个组的时候它们为可迭代对象组。
如此利用这个让条件过滤的是from下img的标签,而form标签没有过滤。
为此弹窗的语句可以用于form标签中。
2、例子举例的img如果标签没了就没有图片就无法操作没有触发条件,为此我们可以想到onfocus这个自动点击来触发条件。但是onfocus只在input中使用。所以我们使用form与input满足name相同为同一集合,可是因为input标签的属性全被过滤了,为此触发弹窗语句写在form中。
3、考虑了语句过滤,以及语句存放位置和条件触发onfocus。==这里有个小知识点就是想要触发onfocus属性那么这元素要被允许被聚焦。==因为只有对其聚焦后onfocus的获得焦点时发生的事件才会触发,input是输入框所有自己带了聚焦功能,而要一个元素具有聚焦功能要使用tabindex属性。
tabindex;全局属性,指示其元素是否可以聚焦,以及它是否/在何处参与顺序键盘(通常使用tab键,因此得名)
相关官方文档:https:///tags/att_standard_tabindex.asp 去瞧瞧
因此尝试输入
#<form tabindex=1 onfocus="alert(1)"autofocus ><input name=attributes><input name=attributes></form>
- 1
虽然这种成功跳出弹窗了,但是因为这个是自动将你的鼠标自动对焦,所以点击确定会一直进行弹窗,所以我们可以在它执行成功一次之后将他移除。
#<form tabindex=1 onfocus="alert(1);this.removeAttribute('onfocus')"autofocus="true"><input name=attributes><input name=attributes></form>
- 1
- 2
浏览器如果没有弹窗,可以换一个浏览器。【下面这个我用的ie】
没有继续弹窗了
2、不进入循环
原因同一个循环不进循环一样的道理。
#<svg><svg onload="alert(1)">
- 1