2024 年 PHP 日本大会

SQL 注入

SQL 注入是一种攻击技术,攻击者利用构建动态 SQL 查询的应用程序代码中的漏洞。攻击者可以访问应用程序的权限部分,检索数据库中的所有信息,篡改现有数据,甚至在数据库主机上执行危险的系统级命令。当开发人员在其 SQL 语句中连接或插入任意输入时,就会出现此漏洞。

示例 #1 将结果集拆分为页面……并创建超级用户 (PostgreSQL)

在以下示例中,用户输入直接插入到 SQL 查询中,允许攻击者在数据库中获得超级用户帐户。

<?php

$offset
= $_GET['offset']; // 注意,没有输入验证!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);

?>
普通用户点击“下一页”、“上一页”链接,其中 $offset 编码到 URL 中。脚本期望传入的 $offset 是一个数字。但是,如果有人尝试通过将以下内容附加到 URL 来入侵呢?
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
    select 'crack', usesysid, 't','t','crack'
    from pg_shadow where usename='postgres';
--
如果发生这种情况,脚本将向攻击者提供超级用户访问权限。请注意,0; 用于为原始查询提供有效的偏移量并终止它。

注意:

使用 --(SQL 中的注释符号)强制 SQL 解析器忽略开发人员编写的查询其余部分是一种常见技术。

绕过搜索结果页面来获取密码是一种可行的方法。攻击者唯一需要做的是查看 SQL 语句中是否使用了任何未正确处理的提交变量。这些过滤器通常可以在前面的表单中设置,以自定义 SELECT 语句中的 WHERE、ORDER BY、LIMITOFFSET 子句。如果您的数据库支持 UNION 结构,则攻击者可能会尝试将整个查询附加到原始查询中,以列出任意表中的密码。强烈建议仅存储密码的安全哈希值,而不是密码本身。

示例 #2 列出文章……以及一些密码(任何数据库服务器)

<?php

$query
= "SELECT id, name, inserted, size FROM products
WHERE size = '
$size'";
$result = odbc_exec($conn, $query);

?>
查询的静态部分可以与另一个 SELECT 语句组合,该语句显示所有密码
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--

UPDATEINSERT 语句也容易受到此类攻击。

示例 #3 从重置密码……到获得更多权限(任何数据库服务器)

<?php
$query
= "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
如果恶意用户提交值 ' or uid like'%admin%$uid 以更改管理员的密码,或者简单地将 $pwd 设置为 hehehe', trusted=100, admin='yes 以获得更多权限,则查询将被扭曲
<?php

// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";

// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;"
;

?>

虽然攻击者必须至少掌握一些数据库架构知识才能成功进行攻击,但这信息通常很容易获得。例如,代码可能是开源软件的一部分,并且公开可用。此信息也可能由闭源代码泄露——即使它被编码、混淆或编译——甚至可能由您自己的代码通过显示错误消息泄露。其他方法包括使用典型的表和列名称。例如,使用名为“users”的表以及列名“id”、“username”和“password”的登录表单。

示例 #4 攻击数据库主机操作系统 (MSSQL Server)

一个可怕的例子,说明如何在某些数据库主机上访问操作系统级命令。

<?php

$query
= "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);

?>
如果攻击者提交值 a%' exec master..xp_cmdshell 'net user test testpass /ADD' --$prod,则 $query 将为
<?php

$query
= "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'"
;
$result = mssql_query($query);

?>
MSSQL Server 执行批处理中的 SQL 语句,包括向本地帐户数据库添加新用户的命令。如果此应用程序作为 sa 运行,并且 MSSQLSERVER 服务以足够的权限运行,则攻击者现在将拥有一个帐户来访问此机器。

注意:

上面的一些示例与特定的数据库服务器相关联,但这并不意味着对其他产品的类似攻击是不可能的。您的数据库服务器可能以其他方式同样容易受到攻击。

A funny example of the issues regarding SQL injection

图片由 » xkcd 提供

规避技术

避免 SQL 注入的推荐方法是通过预处理语句绑定所有数据。使用参数化查询不足以完全避免 SQL 注入,但它是向 SQL 语句提供输入的最简单和最安全的方法。 WHERESETVALUES 子句中的所有动态数据文本都必须替换为占位符。实际数据将在执行期间绑定,并与 SQL 命令分开发送。

参数绑定只能用于数据。SQL 查询的其他动态部分必须针对已知的允许值列表进行过滤。

示例 #5 使用 PDO 预处理语句避免 SQL 注入

<?php

// 动态SQL部分已针对预期值进行验证
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// SQL 使用占位符进行准备
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// 使用 LIKE 通配符提供值
$stmt->execute(["%{$productId}%"]);

?>

PDO、MySQLi和其他数据库库都提供预处理语句。PDOMySQLi

SQL注入攻击主要基于利用代码未考虑安全性的漏洞。永远不要信任任何输入,尤其来自客户端的输入,即使它来自选择框、隐藏输入字段或cookie。第一个例子表明,如此简单的查询都可能造成灾难。

纵深防御策略包含几种良好的编码实践

  • 切勿以超级用户或数据库所有者的身份连接到数据库。始终使用具有最小权限的自定义用户。
  • 检查给定的输入是否具有预期的数据类型。PHP 具有广泛的输入验证函数,从变量函数字符类型函数(例如 is_numeric()ctype_digit())中最简单的函数,到Perl兼容正则表达式支持。
  • 如果应用程序期望数值输入,请考虑使用ctype_digit()验证数据,使用settype()静默更改其类型,或使用sprintf()的数值表示。
  • 如果数据库层不支持绑定变量,则使用数据库特定的字符串转义函数(例如 mysql_real_escape_string()sqlite_escape_string()等)对传递给数据库的每个非数值用户提供的数值进行引用。像addslashes()这样的通用函数仅在非常具体的环境(例如,在禁用NO_BACKSLASH_ESCAPES的单字节字符集中的MySQL)中才有用,因此最好避免使用它们。
  • 不要以任何方式打印任何数据库特定信息,尤其关于模式的信息。另见错误报告错误处理和日志记录函数

除此之外,您还可以通过在脚本中或由数据库本身进行查询日志记录来获益,如果它支持日志记录的话。显然,日志记录无法阻止任何有害的尝试,但它有助于追溯哪些应用程序已被绕过。日志本身并没有用,但它包含的信息很有用。通常情况下,更详细的信息比更少的信息更好。

添加注释

用户贡献的注释 1 条注释

42
Richard dot Corfield at gmail dot com
13年前
最好的方法必须是参数化查询。这样,用户输入的内容无关紧要,数据作为值传递到数据库。

网上快速搜索显示PHP中的一些可能性,这很棒!甚至在这个网站上 - https://php.net/manual/en/pdo.prepared-statements.php
这也说明了为什么这对于安全性和性能都很好。
To Top