# 十一、SQL 注入

> 作者：Peter Yaworski

> 译者：[飞龙](https://github.com/)

> 协议：[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)

## 描述

SQL 注入，或者 SQLi 允许黑客将 SQL 语句注入到目标中并访问它们的数据库。它的潜力是无穷的，通常使其成为高回报的漏洞，例如，攻击者能够执行所有或一些 CURD 操作（创建、读取、更新、删除）来获取数据库信息。攻击者甚至能够完成远程命令执行。

SQLi 攻击通常是未转义输入的结果，输入被传给站点，并用作数据库查询的一部分。它的一个例子是：

```php
$name = $_GET['name'];
$query = "SELECT * FROM users WHERE name = $name";
```

这里，来自用户输入的传入值直接被插入到了数据库查询中。如果用户输入了`test' or 1=1`，查询就会返回第一条记录，其中`name = test or 1=1`，所以为第一行。现在在其他情况下，你可能会得到：

```php
$query = "SELECT * FROM users WHERE (name = $name AND password = 12345");
```

这里，如果你使用了相同的载荷，你的语句最后会变成：

```php
$query = "SELECT * FROM users WHERE (name = 'test' OR 1=1 AND password = 12345");
```

所以这里，查询会表现得有些不同（至少是 MySQL）。我们会获取所有记录，其中名称是`test`，或者密码是`12345`。很显然我们没有完成搜索数据库第一条记录的目标。因此，我们需要忽略密码参数，并能够使用注释来实现，`test' or 1=1;-- `。这里，我们所做的事情，就是添加一个分号来合理结束 SQL 语句，并且立即添加两个短横线（和一个空格）来把后面的所有东西标记为注释。因此不会被求职。它的结果会和我们初始的例子一样。

## 示例

### 1\. Drupal SQL 注入

难度：中

URL：任意版本小于 7.32 的 Drupal 站点

报告链接；`https://hackerone.com/reports/31756`

报告日期：2014.10.17

奖金：$3000

描述：

Drupal 是一个流行的内容管理系统，用于构建网站，非常相似于 WordPress 和 Joomla。它以 PHP 编写，并且基于模块，意思是新的功能可以通过安装模块来添加到 Drupal 站点中。Drupal 社区已经编写了上千个，并且使他们可免费获取。其中的例子包括电子商务，三方继承，内容产品，以及其他。但是，每个 Drupal 的安装都包含想用的核心模块系列，用于运行平台，并且需要数据库的链接。这些通常都以 Drupal 核心来指代。

在 2014 年，Drupal 安全小组为 Drupal 核心发布了一个紧急安全更新，表明所有 Drupal 站点都存在 SQL 注入漏洞，它能够由匿名用户来完成。这一漏洞允许攻击者控制任意没有更新的 Drupal 站点。

对于漏洞来说， Stefan Horst 发现了 Drupal 开发者不当实现了数据库查询的包装功能，它能够被攻击者滥用。更具体来说，Drupal 使用 PHP 数据对象（PDO）作为结构用于访问数据库。Drupal 核心的开发者编写了代码来调用这些 PDO 函数，并且在其他开发者编写代码来和 Drupal 数据库交互的任何时候，这些代码都可以使用。这在软件开发中是个最佳时间。它的原因是为了让 Drupal 能够用于不同类型的数据库（MySQL、Postgres，一起其它），移除复杂性并提供标准化。

现在结果是，Stefan 发现了 Drupal 包装器代码对传给 SQL 查询的数组数据做了一个错误的假设。这里是原始代码：

```php
foreach ($data as $i => $value) {
    [...]
    $new_keys[$key . '_' . $i] = $value;
}
```

你能够之处错误（我都不能）嘛？开发者的假设为，数组数据始终含有数字键，例如`0, 1, 2`以及其他（`$i`的值）。并且所以它们将`$key`变量连接到`$i`，并且使其等于`value`。这里是来自 Drupal 的`db_query`函数，通常的查询的样子。

```php
db_query("SELECT * FROM {users} WHERE name IN (:name)", array(':name'=>array('user1','user2')));
```

这里，`db_query`函数接受数据库查询`SELECT * FROM {users} WHERE name IN (:name)`，以及值的数组来替换查询中的占位符。在 PHP 中，当你将数组声明为`array('value','value2',value3')`，它实际上创建了`[0 =>'value',1=>'value2',2=>'value3']`，其中每个值都可以通过数字键来访问。所以这里，`:name`变量被数组中的值替换。你从中获取到的东西是：

```sql
SELECT * FROM users WHERE name IN (:name_0, :name_1)
```

到目前为止很好。当你获取不含有数字键的数组时，问题就来了，像这样：

```php
db_query("SELECT * FROM {users} where name IN (:name)",
array(':name'=>array('test) -- ' => 'user1','test' => 'user2')));
```

这里，`:name`是个数组，它的键是`'test) –', 'test'`。你可以看到为什么嘛？当 Drupal 收到它并且处理数组来创建查询时，我们会得到：

```sql
SELECT * FROM users WHERE name IN (:name_test) -- , :name_test)
```

看出这是为什么可能需要一些技巧，所以让我们过一遍它。基于上面描述的`foreach`，Drupal 会遍历数组中的每个元素。所以，对于第一个迭代`$i = test) –`以及`$value = user1`。现在，`$key`是查询中的`(:name)`，并且和`$i`组合之后，我们得到了`name_test) –`。对于第二个迭代，` $i = test`并且`$value = user2`，所以组合`$key`和`$i`之后，我们得到了`name_test`，结果是个`:name_test`的占位符，它等于`user2`。

现在，知道这些之后，Drupal 包装 PHP PDO 对象的事实就登场了，因为 PDO 允许多重查询。所以，攻击者能够传递恶意输入，例如实际的 SQL 查询来为任何的数组键创建管理员用户，它作为多重查询解释和执行。

> 重要结论

> SQLi 似乎更难于发现，至少基于为了这本书搜索的报告。这个例子很有意思，因为它并不是提交单引号和截断查询。反之，它全部关于 Drupal 的代码如何处理传给内部函数的数组。这并不易于通过黑盒测试发现（其中你并不接触任何代码）。这里的重要结论是，寻找机会来修改传给站点的输入格式，所以在 URL 接受`?name`作为参数的地方，尝试传入类似`?name[]`的数组，来观察站点如何处理。它也可能不会造成 SQLi，但是可能会导致其他有趣的行为。

## 总结

SQLi 对站点来说十分重要和危险。寻找这一类型的漏洞可能导致站点的完整的 CURD 权限。在其他情况下，它可能扩展为远程代码执行。Drupal 的例子实际上是这些例子之一，它们证明了攻击者可以通过漏洞来执行代码。在寻找它们的时候，不要仅仅留意向查询传递未转义单引号和双引号的可能性，也要注意以非预期方式提供数据的可能性，例如在 POST 数据中提交数组参数。
