dom型XSS(主要是和js语言进行交互)
实验环境
源码:
<!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过滤的过程,方法是移除所有属性
for (let el of root.querySelectorAll('*')) {
for (let attr of el.attributes) {
el.removeAttribute(attr.name);
}
}
document.body.appendChild(root); //将移除属性后的root加入到列表中
</script>
</html>
2.代码审计
2.1 decodeURIComponent(location.hash.substr(1))方法
location.hash的作用
![](https://i-blog.csdnimg.cn/blog_migrate/c0e53609c68a5efcb750b67bbdf500d6.png)
当我们在网页中加入 #字符 时,location.hash提取的就是你输入的内容:#字符
location.hash.substr(1):提取下标在1及以后的location.hash内容
![](https://i-blog.csdnimg.cn/blog_migrate/d4fb91d5bd7a40299b693a993181ad99.png)
decodeURIComponent:函数对 URI 组件进行解码
decodeURIComponent(location.hash.substr(1)):解码date中的内容
2.2 document.createElement函数
document.createElement:用于创建标签,按钮,实例等。
document.createElement('div'):用于创建一个<div>标签。
2.3 root.innerHTML = data;
将接收到的data的值放到 root.innerHTML中,即放到root创建的<div>标签中
2.4 querySelectorAll函数
querySelectorAll函数:用于获取子元素。
root.querySelectorAll('*'):用于获取root中子元素
2.5attributes函数
attributes函数:用于获取元素的属性
el.attributes:用于获取root中子元素的属性
2.6 语句分析
for (let el of root.querySelectorAll('*')) { //用于获取root中的所有子元素
for (let attr of el.attributes) { //用于获取root中的所有子元素的属性
el.removeAttribute(name); //用于移除root中的所有子元素的属性
3.开始进行xss测试注入
这里我们使用弹窗注入进行测试
3.1使用script方式进行注入
由于root.innerHTML = data; :将接收到的data的值放到 root.innerHTML中,即放到root创建的<div>标签中
所以我们首先直接进行注入,使用注入语句
<script>alert(1)</script>
注入结果:
![](https://i-blog.csdnimg.cn/blog_migrate/e24adf0d333faefe7285fb968ebc3ecb.png)
生成弹窗失败,但是<div>标签中确实存在弹窗语句,为什么会失败呢?
3.1.1失败原因
由于这里的root使用了innerHTML属性,从而引发了innerHTML的安全问题所以html5设置了innerHTML属性通过script方式无法进行注入。
![](https://i-blog.csdnimg.cn/blog_migrate/f5123fd6c6ef8251397edb20a3175de2.png)
这里是摘自官方文档的解释,虽然无法使用<script>alert(1)</script>直接进行弹窗注入,但依旧可以采用<img src='x' οnerrοr='alert(1)'>的方式进行注入。
3.2使用img方法进行注入
3.2.1直接注入
<img src='x' onerror='alert(1)'>
注入结果:
![](https://i-blog.csdnimg.cn/blog_migrate/6eca9b70a3790a7053f72cc7e6dfa3b6.png)
弹窗失败
3.2.1.1失败原因
for (let el of root.querySelectorAll('*')) {
for (let attr of el.attributes) {
el.removeAttribute(attr.name);
由于上面的代码进行了xss过滤,导致输入的语句中的两个子元素的属性src和onerror被移除。
3.2.2发现问题
我们通过被执行后的代码可知子元素的属性移除的并不彻底,原本的语句中子元素的属性有两个src和onerror可是执行代码后发现原本应该被全部移除的属性却还剩下一个onerror,这可以作为注入的突破口。
产生原因:
![](https://i-blog.csdnimg.cn/blog_migrate/24b0c9bd1db5d3139b7a7716557cf615.png)
解释:如上图由于src和onerror这两个属性被放在同一个数组内,在移除属性时当指针一开始放在第一个属性下(即放在src属性下),src属性被移除,然后onerror前进一位占用了原本为src属性的位置,此时指针依旧指在src被移除的位置上即onerror属性现在的位置上,然后指针后移,发现数组中已经没有其他属性,随后指针弹出,这时onerror属性未被移除,留在了数组中,随后被调用。
3.2.3利用代码问题进行注入
思路:我们现在已经知道了代码的问题,所以可以考虑是否可以通过加入属性的方法致使属性移除不完整,将弹窗语句的两个属性保存下来。
具体操作:
分别给src属性及onerror属性前分别插入两个属性“x”和“xx”,使其在移除时将“x"和 "xx"属性移除将弹窗语句属性保留。
payload:
<img x src='x' xx onerror='alert(1)'>
注意:这里插入的两个属性不能相同,如果相同过滤时二者会按一个属性的形式进行过滤,导致弹窗语句的属性也会被过滤。
执行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/9c4cfc1455ce2fcc13abef5a851ffd91.png)
弹窗语句存在,弹窗成功
3.2.4分析绕过过滤过程
首先在这个过滤数组中存在四个属性分别是“x”“src”“xx”“onerror”,过滤的方式与之前相同,首先指针先指到下标为0的第一个属性“x”处,随后将属性“x”移除,之后指针不动,src属性前移到下标为0处,之后的属性统统前移一个单位,这时指针后移到下标为1处(既此时的xx属性的位置),随即将“xx”属性删除,此时指针不动“onerror”属性前移一位,移动到下标为1处,随后指针后移弹出,这时的数组中刚好存在“src”和“onerror”这两个弹窗语句属性,调用后成功生成弹窗。
![](https://i-blog.csdnimg.cn/blog_migrate/95b244e6162723a6bf9203752fe18c9f.png)
弹窗语句被保留在列表中。
4测试结果
xss测试注入成功。