# 基于DOM的XSS

在本节中，我们将描述基于DOM的跨站脚本（DOM型XSS），讲解如何发现DOM型XSS漏洞，并讨论如何利用不同的源和接收器来利用DOM型XSS。

## 什么是基于DOM的跨站脚本？

基于DOM的XSS漏洞通常发生在JavaScript从攻击者可控制的源（如URL）获取数据，并将其传递到支持动态代码执行的接收器（如`eval()`或`innerHTML`）时。这样，攻击者就可以执行恶意JavaScript，通常能够劫持其他用户的账户。

要实施基于DOM的XSS攻击，你需要将数据放入源中，使其传播到接收器并导致任意JavaScript的执行。

对于DOM型XSS，最常见的源是URL，通常通过`window.location`对象访问。攻击者可以通过在URL的查询字符串和片段部分中嵌入有效载荷来构造一个链接，将受害者引导到一个存在漏洞的页面。在某些情况下，比如在针对404页面或运行PHP的网站时，有效载荷也可以放置在路径中。

有关源与接收器之间污染流的详细解释，请参阅[基于DOM的漏洞](https://web-sec.gitbook.io/wsa/client-side/dom-based)。

## 如何测试基于DOM的跨站脚本

大多数DOM型XSS漏洞可以通过Burp Suite的Web漏洞扫描器快速可靠地找到。手动测试基于DOM的跨站脚本，通常需要使用带有开发者工具的浏览器，如Chrome。你需要逐个检查每个可用的源，并逐个进行测试。

### 测试HTML接收器

要测试HTML接收器中的DOM型XSS，可以将一个随机的字母数字字符串放入源（如`location.search`），然后使用开发者工具检查HTML并找到字符串出现的位置。请注意，浏览器的查看源代码选项不适用于DOM型XSS测试，因为它未考虑JavaScript对HTML所做的更改。在Chrome的开发者工具中，你可以使用`Control+F`（或MacOS上的`Command+F`）在DOM中搜索字符串。

对于字符串出现在DOM中的每个位置，都需要确定上下文。基于这个上下文，改进你的输入以查看它是如何被处理的。例如，如果你的字符串出现在一个双引号属性中，那么尝试在字符串中注入双引号，看看是否可以跳出该属性。

请注意，浏览器对URL编码的处理方式各不相同，Chrome、Firefox和Safari会对`location.search`和`location.hash`进行URL编码，而IE11和Microsoft Edge（Chromium之前的版本）不会对这些源进行URL编码。如果你的数据在处理之前就被URL编码，那么XSS攻击不太可能奏效。

### 测试JavaScript执行接收器

针对基于DOM的XSS进行JavaScript执行接收器测试要稍微困难一些。对于这些接收器，你的输入不一定会出现在DOM中，因此无法对其进行搜索。相反，你需要使用JavaScript调试器来确定输入是否以及如何被发送到接收器。

对于每个潜在的源，如`location`，首先需要在页面的JavaScript代码中找到源被引用的地方。在Chrome的开发者工具中，你可以使用`Control+Shift+F`（或MacOS上的`Command+Alt+F`）搜索页面上的所有JavaScript代码以找到源。

一旦找到源被读取的地方，就可以使用JavaScript调试器添加一个断点，并跟踪源的值如何被使用。可能你会发现源被分配给了其他变量。如果是这种情况，就需要再次使用搜索功能来跟踪这些变量，看看它们是否被传递给了接收器。当找到一个接收来自源的数据的接收器时，你可以使用调试器检查变量的值，方法是将鼠标悬停在变量上，显示其被发送到接收器之前的值。然后，与HTML接收器一样，改进你的输入，看看是否可以成功实施XSS攻击。

### 使用DOM Invader测试DOM型XSS

在实际环境中识别和利用DOM型XSS可能是一个繁琐的过程，通常需要手动遍历复杂的、经过精简的JavaScript。不过，如果使用Burp的浏览器，就可以利用其内置的DOM Invader扩展，它能为你完成大部分繁重的工作。

> **阅读更多**
>
> * [DOM Invader文档](https://portswigger.net/burp/documentation/desktop/tools/dom-invader)

## 利用不同源和接收器进行DOM型XSS利用

原则上，如果存在一条可执行路径，数据可以从源传播到接收器，那么网站就容易受到基于DOM的跨站脚本攻击。在实践中，不同的源和接收器具有不同的属性和行为，这可能会影响可利用性，并决定需要采取什么技术。此外，网站的脚本可能会对数据进行验证或其他处理，在尝试利用漏洞时必须考虑这些处理。有多种与基于DOM的漏洞相关的接收器。详情请参阅这个[列表](https://github.com/0xf4n9x/Web_Security_Academy-zh/blob/master/client-side/dom-based/index.md#%E5%93%AA%E4%BA%9B%E6%8E%A5%E6%94%B6%E5%99%A8%E5%8F%AF%E8%83%BD%E5%AF%BC%E8%87%B4%E5%9F%BA%E4%BA%8EDOM%E7%9A%84%E6%BC%8F%E6%B4%9E%EF%BC%9F)。

`document.write`接收器可与`script`元素配合使用，因此你可以使用一个简单的有效载荷，如下所示：

```html
document.write('... <script>alert(document.domain)</script> ...');
```

> **LAB**
>
> [在使用`location.search`源的`document.write`接收器中的DOM型XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink)

但请注意，在某些情况下，写入`document.write`的内容可能会包含一些周围的上下文，在利用时需要考虑到这些上下文。例如，在使用JavaScript有效载荷之前，可能需要关闭一些现有元素。

> **LAB**
>
> [在select元素内使用`location.search`源的`document.write`接收器中的DOM型XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-document-write-sink-inside-select-element)

`innerHTML`接收器在任何现代浏览器上都不接受`script`元素，也不会触发`svg onload`事件。意味着你需要使用`img`或`iframe`等替代元素。`onload`和`onerror`等事件处理器可以与这些元素结合使用。例如：

```html
element.innerHTML='... <img src=1 onerror=alert(document.domain)> ...'
```

> **LAB**
>
> [在使用`location.search`源的`innerHTML`接收器中的DOM型XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-innerhtml-sink)

### 第三方依赖中的源和接收器

现代Web应用程序通常使用大量的第三方库和框架构建，这些库和框架通常为开发人员提供额外的功能和能力。重要的是要记住，其中一些也可能是DOM型XSS的潜在源和接收器。

#### jQuery中的DOM型XSS

如果使用的是jQuery之类的JavaScript库，则要留意是否有可以更改页面上DOM元素的接收器。比如，jQuery的`attr()`函数可以更改DOM元素的属性。如果数据是从用户控制的源（如URL）读取的，然后传递给`attr()`函数，那么就有可能操纵发送的值，从而导致XSS。例如，下面的JavaScript，它使用URL中的数据更改了锚元素的`href`属性：

```javascript
$(function() {
	$('#backLink').attr("href",(new URLSearchParams(window.location.search)).get('returnUrl'));
});
```

你可以通过修改URL，使`location.search`源包含恶意JavaScript URL来利用这个漏洞。当页面的JavaScript将此恶意URL应用到返回链接的`href`后，点击返回链接将执行该URL：

```
?returnUrl=javascript:alert(document.domain)
```

> **LAB**
>
> [在使用`location.search`源的jQuery锚`href`属性接收器中的DOM型XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-jquery-href-attribute-sink)

另一个需要留意的潜在接收器是jQuery的`$()`选择器函数，它可以用于向DOM注入恶意对象。

jQuery曾经非常流行，一个典型的DOM型XSS漏洞就是由于网站将该选择器与`location.hash`源结合使用，以实现动画或自动滚动到页面上特定的元素而造成的。这种行为通常是使用一个易受攻击的`hashchange`事件处理器实现的，类似于以下：

```javascript
$(window).on('hashchange', function() {
	var element = $(location.hash);
	element[0].scrollIntoView();
});
```

由于`hash`是用户可控的，攻击者可以利用它将XSS载体注入到`$()`选择器接收器中。较新版本的jQuery已修补了这一特定漏洞，当输入以哈希字符（`#`）开头时，将无法向选择器中注入HTML。不过，你仍然可以在实际环境中发现易受攻击的代码。

要真正利用这一经典漏洞，你需要找到一种方法，在不需要用户交互的情况下触发`hashchange`事件。其中一个最简单的方法就是通过`iframe`传递你的利用：

```html
<iframe src="https://vulnerable-website.com#" onload="this.src+='<img src=1 onerror=alert(1)>'">
```

在这个例子中，`src`属性指向一个带有空哈希值的易受攻击的页面。当`iframe`加载时，XSS载体会被添加到哈希值中，从而导致`hashchange`事件触发。

> **注意**
>
> 即使是较新版本的 jQuery，只要你对来自源的输入具有完全的控制，并且该源无需`#`前缀，那么它仍然可能通过`$()`选择器接收器而易受到攻击。

> **LAB**
>
> [在使用hashchange事件的jQuery选择器接收器中的DOM型XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-jquery-selector-hash-change-event)

#### AngularJS中的DOM型XSS

如果使用像AngularJS这样的框架，则有可能在没有角括号或事件的情况下执行JavaScript。当一个网站在HTML元素上使用`ng-app`属性时，AngularJS将对其进行处理。在这种情况下，AngularJS将在双花括号内执行JavaScript，双花括号可以直接出现在HTML中，也可以出现在属性内。

> **LAB**
>
> [带有角括号和双引号HTML编码的AngularJS表达式中的DOM型XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-angularjs-expression)

## DOM型XSS结合反射和存储数据

一些纯粹基于DOM的漏洞在单个页面内自包含。如果脚本从URL读取一些数据并将其写入到一个危险的接收器，那么漏洞就完全是客户端的。

不过，源并不仅限于浏览器直接暴露的数据，它们也可以来自网站。例如，网站通常会在服务器的HTML响应中反射URL参数。这通常与普通XSS有关，但也可能导致反射型DOM XSS漏洞。

在反射型DOM XSS漏洞中，服务器会处理来自请求的数据，并将数据回显到响应中。反射的数据可能被放置在JavaScript字符串字面值中，或者在DOM的数据项中，如表单字段。然后页面上的某个脚本会以不安全的方式处理反射的数据，最终将其写入到一个危险的接收器。

```html
eval('var data = "reflected string"');
```

> **LAB**
>
> [反射型DOM XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-dom-xss-reflected)

网站可能也会将数据存储在服务器上，并在其他地方反射它。在一个存储型DOM XSS漏洞中，服务器从一个请求接收数据，存储它，然后在后续的响应中包含这些数据。后续响应中的某个脚本包含一个接收器，然后该接收器以不安全的方式处理数据。

```html
element.innerHTML = comment.author
```

> **LAB**
>
> [存储型DOM XSS](https://portswigger.net/web-security/cross-site-scripting/dom-based/lab-dom-xss-stored)

## 哪些接收器可能导致DOM型XSS漏洞？

以下是一些可能导致DOM型XSS漏洞的主要接收器：

```
document.write()
document.writeln()
document.domain
element.innerHTML
element.outerHTML
element.insertAdjacentHTML
element.onevent
```

以下jQuery函数也是可能导致DOM型XSS漏洞的接收器：

```
add()
after()
append()
animate()
insertAfter()
insertBefore()
before()
html()
prepend()
replaceAll()
replaceWith()
wrap()
wrapInner()
wrapAll()
has()
constructor()
init()
index()
jQuery.parseHTML()
$.parseHTML()
```

## 如何防范DOM型XSS漏洞

除了在[基于DOM的漏洞](https://web-sec.gitbook.io/wsa/client-side/dom-based)中描述的通用措施外，还应避免允许来自任何不受信任的源的数据动态写入到HTML文档。
