目录
1 DOMPurify
1.1 漏洞源码
1.2 加载框架
编辑
setTimeout
1.3 ok?
1.4 window和document
1.5 Overwrite(document.x)
1.6 Overwrite2(document.x.y)
1.6.1 form表单
1.7 toString
2. 设计目的:“重写类型转换逻辑”
3. 延伸:“类型转换” 的场景
1.8 可控字符串a标签的属性等
1.8.1 a href
逐步骤分析结果由来:
最终结果的本质:
打印代码结果:
1.9 alert执行script
1.9.* 和alert的区别
1.10 利用DOM破坏
1.10.1 ok
1.10.2 payload:
1.10.3 setTimeout
1.11 DOM框架过滤了JavaScript恶意代码
1 DOMPurify
1.1 漏洞源码
<h2 id="boomer">Ok, Boomer.</h2>
<script>boomer.innerHTML = DOMPurify.sanitize(new URL(location).searchParams.get('boomer') || "Ok, Boomer")setTimeout(ok, 2000)
</script>
1.2 加载框架
Release DOMPurify 3.2.6 · cure53/DOMPurify · GitHub
可以去GitHub上面将此框架下载下来,加载到这个代码中,并且加载路径写到js之前执行
setTimeout
//这是一个定时器,js定时器有两个,一个是setTimeout 在指定延迟的时间内执行一次,2秒后执行ok
// setInterval 循环执行
1.3 ok?
1 ok从哪里来的,没有进行定义?
2 所以ok大概率是我们的payload恶意代码
3 框架如何过滤呢?(这个框架非常的安全基本上没有绕过的可能性)
4 ok引入
所以思考:我们该如何创建一个OK。让setTimeout执行呢?
1.4 window和document
<body><img id="x"><img name="y">
</body>
<script>console.log(x);console.log(y);console.log(document.x);console.log(document.y);console.log(window.x);console.log(window.y);
window是js所有元素的祖先
document是整个页面
访问看一下打印运行的结果
1.5 Overwrite(document.x)
1.5.1 打印cookie
自带的cookie值,网站:
最开始是没有cookie的
1.5.2 给他重新定义,写一个cookie
<body></body>
<script>let div = document.createElement('div')div.innerHTML = '<img name=cookie>'document.body.appendChild(div)console.log(document.cookie)
</script>
</html>
再次访问页面的cookie
改变了系统自带的cookie值
所以直接覆盖掉了系统函数的document.cookie
1.6 Overwrite2(document.x.y)
1.6.1 form表单
<body><form name="body"><img id="appendChild" src=1 onerror="alert(1)"></form>
</body>
<script>console.log(document.body.appendChild)
</script>
访问弹一,并且取代了系统函数打印出来是这个
1.7 toString
按照以上两种我们获取的都是html的标签elment元素的值,但是其实对于大多数的情况我们需要的应该是一个可控的字符串的类型
-
toString()
是什么?
是 JavaScript 等语言中 对象的内置方法(继承自Object.prototype
),默认行为是返回类似[object 构造函数名称]
的字符串(比如{}.toString()
返回[object Object]
,[].toString()
返回[object Array]
)。 -
“派生类” 指什么?
指 继承自父类的子类(比如自定义类class Dog extends Animal
中,Dog
就是Animal
的派生类)。
2. 设计目的:“重写类型转换逻辑”
-
默认逻辑的不足:
原生Object.prototype.toString()
只反映对象的 “类型标签”(如Object
/Array
),但实际开发中,我们希望对象转成字符串时 携带更具体的信息(比如person.toString()
返回"{name: 'Alice', age: 20}"
)。 -
重写的作用:
通过在 派生类中重写toString()
,可以自定义 “对象 → 字符串” 的类型转换规则。例如:javascript
class Person {constructor(name) { this.name = name; }// 重写 toString,自定义字符串转换逻辑toString() { return `Person: ${this.name}`; } } const alice = new Person('Alice'); console.log(alice.toString()); // 输出 "Person: Alice"(而非默认的 "[object Object]")
3. 延伸:“类型转换” 的场景
toString()
会在以下 隐式类型转换场景 中被自动调用:
- 拼接字符串时:
console.log('Hello ' + alice)
(等价于'Hello ' + alice.toString()
)。 - 使用
String()
函数转换对象:String(alice)
(内部调用alice.toString()
)。
因此,重写 toString()
本质是 定制对象在这些场景下的表现,属于 “类型转换逻辑” 的一部分。
简单总结:toString()
让对象能自己决定 “怎么变成字符串”,派生类重写它,就是为了让转换结果更符合业务需求
要将基本的 Object.prototype.toString() 用于重写的对象(或者在 null 或 undefined 上调用它),你需要在它上面调用 Function.prototype.call() 或者 Function.prototype.apply(),将要检查的对象作为第一个参数传递(称为 thisArg)。
1.8 可控字符串a标签的属性等
1.8.1 a href
a标签的href属性里面可以输入可控的字符串,所以我们可以利用这个来进行,来获取heref的内容
所以我们可以通过以下代码来进行 fuzz 得到可以通过 toString 方法将其转换成字符串类型的标签
Object.getOwnPropertyNames(window).filter(p => p.match(/Element$/)).map(p => window[p]).filter(p => p && p.prototype && p.prototype.toString !== Object.prototype.toString)
逐步骤分析结果由来:
-
Object.getOwnPropertyNames(window)
先获取window
对象的所有 “自有属性名”(字符串形式),包括可枚举和不可枚举属性(如各种 DOM 构造函数、全局方法等)。 -
.filter(p => p.match(/Element$/))
筛选出属性名 以 “Element” 结尾 的字符串。
原因:DOM 元素的构造函数通常以此结尾(如HTMLElement
、HTMLDivElement
、HTMLImageElement
、SVGElement
等)。 -
.map(p => window[p])
将上一步筛选出的属性名,转换为对应的window
对象属性值 —— 即 DOM 元素构造函数本身(如window.HTMLDivElement
对应HTMLDivElement
构造函数)。 -
.filter(p => p && p.prototype && p.prototype.toString !== Object.prototype.toString)
最终筛选出满足以下条件的构造函数:p
存在(排除 null/undefined);p.prototype
存在(构造函数有原型对象);- 原型上的
toString
方法 不是Object
原生的toString
(即该方法被重写过)。
最终结果的本质:
返回所有 “名称以 Element 结尾” 且 “原型重写了 toString 方法” 的 DOM 元素构造函数。
这些构造函数对应的 DOM 元素(如 <div>
、<img>
等),其 toString
行为已被定制(而非使用 Object
的默认实现),例如 HTMLDivElement
实例的 toString()
可能返回更具体的字符串(而非 [object Object]
)。
打印代码结果:
分析知道有以下两个:
1.9 alert执行script
<a id="a" href="aaaa"></a>
console.log(a)
结果:
1.9.* 和alert的区别
直接alert(a),他将我们的href属性以string的形式给打印了出来
所以他用alert直接调用了a里面的tostring方法,将href里面的值打印了出来。
你将a当作函数的参数的时候,会自动调用a的tostring方法,而a的tostring是自身方法,不是继承的,他可以打印herf中的内容
1.10 利用DOM破坏
1.10.1 ok
通过以上的分析和拓展我们应该有了一个大概的思路,首先回到1.3:
1 ok可以从a标签的id来,因为通过打印a标签的id可以直接将a标签打印出来
1.10.2 payload:
2 恶意代码payload:<a id=ok href=javascript:alert(1)>
1.10.3 setTimeout
3 这是个函数,查看官方文档解释:他可以执行字符串,就是说我的ok当传入函数的参数,利用a标签自带的tostring方法将href里面的值当字符串传递给setTimeout进行执行
当输入这个恶意代码后,调试可以知道,它将JavaScript伪协议给过滤了:
1.11 DOM框架过滤了JavaScript恶意代码
框架白名单:
那我们思考是否可以利用白名单进行绕过呢,那白名单是否可以弹窗呢?试一下
ok,成功绕过