# SQL注入UNION攻击

当一个应用程序存在SQL注入漏洞，并且查询结果在应用程序的响应中返回时，可以使用`UNION`关键字从数据库的其他表中检索数据。这就是SQL注入UNION攻击。

`UNION`关键字允许你执行一个或多个额外的`SELECT`查询，并将结果追加到原始查询中。例如：

```sql
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
```

此SQL查询将返回一个包含两列的单结果集，其中包含`table1`中`a`和`b`列的值以及`table2`中`c`和`d`列的值。

要使`UNION`查询起作用，必须满足两个关键要求：

* 各个查询必须返回相同数量的列。
* 每列中的数据类型在各个查询之间必须兼容。

要实施SQL注入UNION攻击，需要确保你的攻击满足这两个要求。通常涉及弄清楚：

* 原始查询返回了多少列？
* 从原始查询返回的哪些列适合容纳注入查询的结果？

## 确定SQL注入UNION攻击所需的列数

在进行SQL注入UNION攻击时，有两种有效的方法可以确定原始查询返回了多少列。

第一种方法是注入一系列`ORDER BY`子句，并递增指定的列索引，直到发生错误。例如，假设注入点是原始查询的`WHERE`子句中的带引号字符串，你可以提交：

```sql
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
etc.
```

这一系列的有效载荷修改了原始查询，按结果集中的不同列对结果进行排序。`ORDER BY`子句中的列可以由其索引指定，因此你不需要知道任何列的名称。当指定的列索引超过结果集中的实际列数时，数据库会返回错误，例如：

```sql
The ORDER BY position number 3 is out of range of the number of items in the select list.
```

应用程序实际上可能会在其HTTP响应中返回数据库错误，或者可能返回一个通用错误，或者根本不返回结果。只要你能检测到应用程序响应中的某些差异，就可以推断出查询返回多少列。

第二种方法是提交一系列`UNION SELECT`有效载荷，指定不同数量的空值：

```sql
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
etc.
```

如果空值的数量与列数不匹配，数据库会返回一个错误，例如：

```sql
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
```

同样，应用程序实际上可能会返回此错误消息，或者可能只返回一个通用错误或没有结果。当空值的数量与列数匹配时，数据库将在结果集中返回一个额外的行，其中每列都包含空值。对产生的HTTP响应的影响取决于应用程序的代码。如果运气好的话，你将在响应中看到一些额外的内容，比如HTML表上的额外行。否则，空值可能会触发其他错误，例如`NullPointerException`。最糟糕的情况是，响应可能与由不正确的空值数引起的响应没有区别，使得这种确定列数的方法无效。

> **LAB**
>
> [SQL注入UNION攻击，确定查询返回的列数](https://portswigger.net/web-security/sql-injection/union-attacks/lab-determine-number-of-columns)

> **注意**
>
> * 使用`NULL`作为注入的`SELECT`查询返回的值的原因是，原始查询和注入查询之间的每个列的数据类型必须兼容。由于`NULL`可以转换为常用的每种数据类型，使用`NULL`可以最大程度地提高在列数正确时有效载荷成功的机会。
> * 在Oracle上，每个`SELECT`查询必须使用`FROM`关键字并指定一个有效的表。在Oracle上有一个名为`dual`的内置表，可以用于此目的。因此，在Oracle上注入的查询将需要如下所示：
>
>   `' UNION SELECT NULL FROM DUAL--`
> * 所描述的有效载荷使用双破折号注释序列`--`来注释掉注入点后的原始查询的其余部分。在MySQL上，双破折号序列必须跟随一个空格。或者，哈希字符`#`可用于标识注释。
>
> 有关数据库特定语法的更多详细信息，请参阅[SQL注入速查表](https://web-sec.gitbook.io/wsa/server-side/sql-injection/cheat-sheet)。

## 在SQL注入UNION攻击中查找具有有用数据类型的列

进行SQL注入UNION攻击的原因是能够从注入的查询中检索结果。通常，希望检索的感兴趣的数据是以字符串形式存在，因此需要在原始查询结果中找到一个或多个数据类型为字符串数据或与之兼容的列。

在已确定所需列数后，你可以通过提交一系列`UNION SELECT`有效载荷，依次将字符串值放入每一列中，来测试它是否可以容纳字符串数据。例如，如果查询返回四列，你将提交：

```sql
' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--
```

如果某列的数据类型与字符串数据不兼容，注入的查询将导致数据库错误，例如：

```
Conversion failed when converting the varchar value 'a' to data type int.
```

如果没有发生错误，并且应用程序的响应包含一些额外的内容，包括注入的字符串值，则相关列适合检索字符串数据。

> **LAB**
>
> [SQL注入UNION攻击，查找包含文本的列](https://portswigger.net/web-security/sql-injection/union-attacks/lab-find-column-containing-text)

## 使用SQL注入UNION攻击检索感兴趣的数据

当确定了原始查询返回的列数，并找到可以容纳字符串数据的列时，你就可以检索感兴趣的数据。

假设：

* 原始查询返回两列，两列都可以容纳字符串数据。
* 注入点是原始查询`WHERE`子句中的引号字符串。
* 数据库中包含一个名为`users`的表，具有`username`和`password`列。

在这种情况下，你可以通过提交以下输入来检索`users`表的内容：

```sql
' UNION SELECT username, password FROM users--
```

当然，实施此攻击所需的关键信息是存在一个名为`users`的表，该表具有名为`username`和`password`的两列。如果没有这些信息，你将尝试猜测表和列的名称。实际上，所有现代数据库都提供了检查数据库结构的方法，以确定其中包含的表和列。

> **LAB**
>
> [SQL注入UNION攻击，从其他表中检索数据](https://portswigger.net/web-security/sql-injection/union-attacks/lab-retrieve-data-from-other-tables)

> **阅读更多**
>
> [在SQL注入攻击中检查数据库](https://portswigger.net/web-security/sql-injection/examining-the-database)

## 在单列中检索多个值

在前面的示例中，假设查询仅返回单个列。

你可以通过将多个值拼接在单个列中，来轻松检索这些值，最好包括一个适当的分隔符，以便可以区分组合值。例如，在Oracle上，可以提交以下输入：

```sql
' UNION SELECT username || '~' || password FROM users--
```

这里使用了双管道序列`||`，它是Oracle上的字符串拼接运算符。注入的查询将`username`和`password`字段的值用`~`字符分隔合并在一起。

查询的结果将让你读取所有的用户名和密码，例如：

```
...
administrator~s3cure
wiener~peter
carlos~montoya
...
```

请注意，不同的数据库使用不同的语法来进行字符串拼接。有关详细信息，请参阅[SQL注入速查表](https://web-sec.gitbook.io/wsa/server-side/sql-injection/cheat-sheet)。

> **LAB**
>
> [SQL注入UNION攻击，在单个列中检索多个值](https://portswigger.net/web-security/sql-injection/union-attacks/lab-retrieve-multiple-values-in-single-column)
