如何在不使用 PDO 或 PHP 应用程序绑定的情况下解决二阶 SQL 注入

Posted

技术标签:

【中文标题】如何在不使用 PDO 或 PHP 应用程序绑定的情况下解决二阶 SQL 注入【英文标题】:How to address second order SQL injection without using PDO or binding in PHP application 【发布时间】:2020-12-30 12:56:36 【问题描述】:

如何在 php 应用程序中不使用 PDO 或 mysqli 绑定来解决二阶 SQL 注入问题。我做了很多研究,但都指向我没有的 PDO。 mysql_real_escape_string PHP 转义只是为了保护引号。 现在我们有使用 mysql_connect 的遗留 PHP。我们无法升级我们的系统以使用 PDO 或 MySQLi,因为它需要安装依赖项。为了结束注入,我们使用了逃逸。但这并不能防止二阶 SQL 注入。

$name = $_POST['name']; $user = "从 name = $name 的用户中选择用户"

现在使用 $id 来获取用户位置。请注意,当前代码不会加入,因为这只是示例。

$loc = "Select * from location where user = $user"

目前我们对 $name 和 $user 使用转义来防止注入。但问题是,如果来自第一个查询的查询包含 SQL 查询,那么我们就有问题了。所以我们需要一种方法来逃避查询,这样第二个查询就安全了

【问题讨论】:

mysql_real_escape_string PHP escape is just for protection against the quotes. 谁跟你说这些废话的? mysql_ 也被弃用了很长一段时间,所以你似乎在那里有非常过时的设置 一阶或n阶sql注入没有区别。保护都是一样的 应该对您要插入 SQL 的每个 string 进行转义。不管是一阶还是二阶。只有一次 SQL 注入。但是,使用 PHP 5 是非常不负责任的。如果您认真对待您的用户,您应该尽一切努力尽快升级。 上面的 cmets 是错误的,他们显然没有阅读您的代码示例。他们很懒惰,只是自动关闭几乎所有标记为 PHP/SQL 注入的问题,作为***.com/questions/60174/… 的副本。那个很棒,它回答了大多数 SQL 注入问题,但它不包括你的情况。 【参考方案1】:

在将整个子查询插入位置查询时,您不能使用 mysql_real_escape_string() 是正确的。

$name = $_POST['name'];

$user = "Select user from user where name = $name"

$user_esc = mysql_real_escape_string($user); -- WRONG

$loc = "Select * from location where user = $user"

Advice to defend against SQL injection 通常归结为“使用查询参数”,这在很多情况下是正确的,但在这种情况下它不起作用。

转义字符串函数和绑定查询参数都仅用于保护 SQL 表达式中的单个值。这些对于动态 SQL 查询的其他部分都没有用处:

表名或列名等标识符 值列表(例如在IN(...) 谓词中) SQL 关键字 SQL 表达式 整个 SQL 子查询,与您的情况一样。

在您的情况下,您的子查询是您的应用程序控制下的固定字符串,但 $name 变量除外。如果您对该变量进行转义,那么它就不会受到 SQL 注入的影响。

$name = $_POST['name'];
$name_esc = mysql_real_escape_string($name);

$user_query = "Select user from user where name = '$name_esc'"

然后您可以将该子查询用作第二个查询的一部分,并且没有 SQL 注入的风险。

$loc_query = "Select * from location where user IN ($user_query)"

顺便说一下,我做了两个小改动:

将子查询放在括号内 使用IN( ) 而不是=。如果子查询的结果可以返回多于一行,则不允许使用=

我还建议您学习如何在 SQL 中使用联接。这是使用 SQL 的一种普通且推荐的方式。通常,如果表的索引正确,则使用连接的查询比使用子查询的查询具有更好的性能,即使两个查询产生相同的结果。这是一个例子:

$name = $_POST['name'];
$name_esc = mysql_real_escape_string($name);

$loc_query = "Select loc.* from user join loc using (user) 
    where user.name = '$name_esc'";

我可能会因为给你一个适用于 mysql_real_escape_string() 的解决方案而被否决,因为该函数已被弃用,并且已从当前版本的 PHP 中删除。

我确实建议升级到当前版本的 PHP,并且我确实建议使用 PDO 和查询参数。它们更简单、更安全且性能更好。

$loc_query = "Select loc.* from user join loc using (user) 
    where user.name = ?";

$stmt = $pdo->prepare($loc_query);
$stmt->execute( [ $_POST['name'] ] );

使用查询参数时无需转义 post 变量。

您还会发现 PHP 7+ 通常比 PHP 5.x 具有更好的性能。

不能升级是不正确的。只是需要一些工作。

【讨论】:

您所说的“转义字符串函数 ... 仅用于保护 SQL 表达式中的单个值”是不正确且具有误导性的。不是“值”,而是 字符串文字. 或引用的日期文字。或引用的数字文字(奇数但允许)。我相信您知道我同意使用查询参数是一种优越的解决方案。但是 OP 可能处于这样一种情况,即他们的老板没有预算来进行升级所需的工作。 mysql 中没有“日期文字”之类的东西。也没有“引用的数字文字”。它是数字文字或字符串文字。 A string is a sequence of bytes or characters, enclosed within either single quote (') or double quote (") characters.,不管里面的特殊字符是什么。 @user332951 我的回答对您有帮助吗?如果是这样,请记住赞成或接受答案是合适的。见***.com/help/someone-answers

以上是关于如何在不使用 PDO 或 PHP 应用程序绑定的情况下解决二阶 SQL 注入的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 PDO 在 PHP 中绑定 bigint 值?

PHP PDO 清理用户输入

PHP / PDO动态绑定值(无效的参数计数错误)

如何将 ISO8601 TSQL DATETIME 参数与 PDO 绑定?

PHP PostgreSQL PDO 无法使用 LIKE 绑定参数

PHP PDO - 绑定表名? [复制]