如何在不使用 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 注入的主要内容,如果未能解决你的问题,请参考以下文章
如何将 ISO8601 TSQL DATETIME 参数与 PDO 绑定?