65.9K
CodeProject 正在变化。 阅读更多。
Home

SecurePHPWebAppCoding - SQL 注入——它是什么以及如何停止?

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (9投票s)

2014年9月15日

CPOL

7分钟阅读

viewsIcon

32147

本文介绍我们在开发 Web 应用程序时如何编写代码导致 SQL 注入漏洞,以及我们应该如何编写代码来防止它。

引言

在本文中,我试图涵盖有关 SQL 注入的一些基本信息,我们在开发 Web 应用程序时如何编写代码导致 SQL 注入漏洞,攻击者如何利用此漏洞获得未经授权的访问,以及我们如何稍微更改代码来纠正我们的错误并防止攻击者在 Web 应用程序中使用 SQL 注入,从而使我们的应用程序更安全。本文使用 PHP 和 MySQL 来举例说明,但其他语言也有类似的函数来防止 SQL 注入。那么,让我们看看什么是 SQL 注入。

什么是 SQL 注入?

SQL 注入是一种 Web 应用程序漏洞,攻击者可以利用这种漏洞操纵并提交 SQL 命令,从数据库中检索未经授权的信息。这种攻击通常发生在 Web 应用程序在未验证或转义用户提供的数据的情况下执行它时。SQL 注入可以使攻击者访问敏感信息,如财务数据、信用卡信息或用户的个人信息,并允许攻击者操纵存储在数据库中的数据。这不是数据库或 Web 应用程序服务器的问题,而是 Web 应用程序编程问题,大多数开发人员对此并不了解。

攻击者使用 SQL 注入可以实现什么?

根据应用程序以及应用程序如何处理用户数据,SQL 注入攻击用于以下目的。还有其他情况。

  • 未经授权的登录:攻击者可以使用 SQL 注入获得用户账户的未经授权访问,并执行他们在该账户上的任何操作。
  • 权限提升:具有较低权限的用户可以使用 SQL 注入登录到具有比其账户更高权限的账户,并为其账户添加更多权限,以便攻击者可以访问该应用程序的更多数据/功能。
  • 篡改数据库数据:攻击者可以更新数据库数据以更改其他配置文件、更改密码,这将给其他用户带来问题。
  • 转储数据库:攻击者可以使用 SQL 注入转储数据库中的所有数据,并公开包含用户登录信息、信用卡信息等敏感信息。
  • 删除/破坏数据:SQL 注入可用于从数据库中删除数据,导致网站丢失所有用户记录及其所有详细信息。
  • 读取 Web 服务器文件:攻击者可以使用 SQL 注入加载 Web 服务器中的文件并读取应用程序代码、配置文件等。
  • 损害公司声誉:SQL 注入可用于转储所有数据,并可以公开可用。没有用户喜欢他们的个人/敏感数据泄露。

我们如何防止 SQL 注入?

  • 切勿信任用户输入和客户端验证。始终在服务器端验证用户输入是否为特定数据类型,或在使用查询之前将数据转换为特定数据类型。
  • 对于 string 数据,转义单引号和双引号,或将 string 转换为 HTML 实体(这将增加 string 的长度,因此取决于字段类型/长度,请使用它)。
  • 尽量避免使用 string 连接来创建查询。这是使 Web 应用程序容易受到 SQL 注入攻击的主要原因之一,但大多数开发人员都使用这种方法来生成查询,因为他们觉得它很容易,而没有考虑或了解他们正在犯的错误。
  • 使用预编译语句和参数绑定。
  • 尽可能,从用户输入数据中替换数据库中潜在的危险字符。
    特殊数据库字符 数据库中的函数
    ; 查询分隔符
    ' 字符数据字符串分隔符
    --, # 单行注释
    /* */ 多行注释

    注意:特殊数据库字符可能因数据库而异。

  • 使用权限较低的账户来执行 Web 应用程序的查询。

现在让我们做一些实际工作。让我们看看我们是如何编写代码,让黑客能够在网站上使用 SQL 注入,同时我们还将看看如何用那段代码再写几行代码来防止网站上的 SQL 注入。我们将使用 PHP 来展示,但同样的事情也可以在/使用其他编程语言编写的应用程序中完成。那么,让我们开始吧。让我们先看看每个人在询问 SQL 注入时都会给出的经典示例。

登录页面中的 SQL 注入

当我们实现登录功能时,大多数开发人员会这样写

$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '" . $username . "' AND password = '" . $password . "'";

因此,当用户在登录表单中提供他们的用户名(username@example.com)和密码(password)时,该脚本会生成如下查询:

$query = "SELECT * FROM users WHERE username = 'username@example.com' AND password = 'password'";

Web 应用程序执行查询,验证用户凭据并允许用户登录。但是,当攻击者输入密码“' or '1'='1”(不带双引号)时,查询将转换为:

$query = "SELECT * FROM users WHERE username = 'username@example.com' AND password = '' or '1'='1'";

这使得攻击者无需知道用户的密码即可登录。但是您可能会说,我们大多使用哈希来存储密码。那么生成的查询将是这样的:

$username = $_POST['username'];
$password = sha1($_POST['password']);
$query = "SELECT * FROM users WHERE username = 'username@example.com' AND password = '154eec809fb37f6944eccf1fa9c6c981eb15d063'";

这将阻止用户登录。是的,这是真的。但是,当用户输入用户名“' or 1; -- ”或“' or 1;#”(不带双引号)时,现在的登录查询看起来像:

$query = "SELECT * FROM users WHERE username = '' or 1; -- ' AND password = '' or '1'='1'";

在 MySQL 中,“ -- ”(请注意 -- 前后的空格,如果没有空格则会出错)或“#”(如果未包含在引号中)之后的所有内容都被忽略,因为它被视为注释。因此,它将允许用户登录到任意账户,而账户所有者对此一无所知。

那么我们如何避免这种情况呢?

  • 转义所有单引号和双引号。
$username = $_POST['username'];
$password = sha1($_POST['password']);
$query = "SELECT * FROM users WHERE username = 'username@example.com' AND password = '\' or \'1\'=\'1'";

这将检查“' or '1'='1”作为密码,因为单引号被转义了,除非用户将其设置为自己的密码,否则它显然不会与用户密码匹配。

  • 不允许使用对数据库而言潜在危险的字符,并在可能的情况下从用户提供的输入数据中删除这些字符。而不是编写如下所示的代码
$username = $_POST['username'];
$password = sha1($_POST['password']);
$query = "SELECT * FROM users WHERE username = '' or 1; -- ' AND password = '' or '1'='1'";

我们可以这样写:

$username = str_replace(array(' -- ', '#', ';'), array('', '', ''), $_POST['username']);
$username = addslashes($username);
$password = sha1($_POST['password']);
$query = "SELECT * FROM users WHERE username = '\' or 1' AND password = '154eec809fb37f6944eccf1fa9c6c981eb15d063'";

这将阻止用户登录,因为用户名无效。

  • 不要使用 string 连接创建查询。使用预编译语句和参数绑定。
$username = $_POST['username'];
$password = sha1($_POST['password']);
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param('ss', $username, $password);

它会生成一个查询,其中所有单引号和双引号都被转义(如果在提供的参数中存在单引号和双引号,则会在其前面添加一个 斜杠(\)),并且引号被视为用户提供的输入数据中的字符,而不是字符数据字符串分隔符,从而防止 SQL 注入。

让我们再举一个例子。

使用 URL 中作为查询字符串的 Product Id 显示产品信息

假设您已经实现了一个功能,在该功能中您正在列出某个类别下的所有产品。当用户单击某个产品时,您需要显示该产品的详细信息。当用户单击产品时,您会将用户重定向到一个 URL 如下的页面:

http://www.website.com/product_details.php?product_id=1

在产品详细信息页面,用于获取产品的代码如下:

$product_id = $_GET['product_id'];
$sql = 'SELECT * FROM products WHERE id = ' . $product_id;

当查询 string 中的 product_id 被更改为“1 or 1 = 1”(不带双引号)时,查询将是:

$sql = 'SELECT * FROM products WHERE id = 1 or 1 = 1';

这将返回所有产品。但这在这种情况下不是最危险的情况。攻击者可以在此处注入 UNION 查询,如下所示:

http://www.website.com/product_details.php?product_id=-1 union select * from users

查询将是:

$sql = 'SELECT * FROM products WHERE id = -1 union select * from users';

这将从 users 表中返回详细信息。

上述查询仅在以下情况下有效:

  • 两个表具有相同数量的列
  • 列的数据类型相应匹配

注意:注入 UNION 查询以返回精确所需详细信息需要几个步骤,本文将不涵盖。

那么现在让我们看看我们在这里可以做什么。我们知道产品 ID 总是整数(至少大多数情况下)。因此,为了在这种情况下避免 SQL 注入,请将用户数据转换为准确的数据类型。所以在这个例子中:

$product_id = intval($_GET['product_id']);
$sql = 'SELECT * FROM products WHERE id = ' . $product_id;

如果我们提供“1 or 1 = 1”或“-1 union select * from users”,它将被转换为整数值,这将防止 SQL 注入。

有很多使用 SQL 访问数据库的场景,在这里无法全部涵盖。上面,我们看到最可能出现和最常见的案例,我们可以与其他案例进行比较,并使用上面解释的方法来防止 SQL 注入。上面的一种方法可能不足以始终防止 SQL 注入。我们可能需要组合使用以避免 SQL 注入。但最好的解决方案是替换用户提供的数据中所有不必要的字符,验证/将数据转换为最合适的数据类型,并使用参数绑定。

结论

Web 应用程序中的 SQL 注入主要发生是因为开发人员在编写代码时的疏忽。我们编写代码是为了使应用程序能够工作。但同时,我们也应该编写代码来保护应用程序免受未经授权的访问,这可以通过在开发 Web 应用程序时付出一点额外的努力来完成。

© . All rights reserved.