从 PHP 代码调用存储过程会阻止 SQL 注入吗?

Does calling a stored procedure from PHP code prevent SQL injection?

提问人:Watachiaieto 提问时间:6/8/2023 最后编辑:DharmanWatachiaieto 更新时间:8/10/2023 访问量:124

问:

我对存储过程以及如何使用它们来防止 SQL 注入感到非常困惑。我在网上读到的一切似乎都是矛盾的,每个地方都举了一个似乎不起作用的例子。

这是否安全,不会受到SQL注入的影响?

PROCEDURE user_get_by_id (IN user_id INT)
BEGIN
    SELECT id, email, name, password_hash FROM users WHERE id=user_id;
END

我运行过的一些测试... 它适用于 int 的 id... 它适用于作为字符串的 int 的 id... 如果我使用字符串,它会说一些关于字段的事情 - 但我只是想让它识别它不是 int?我尝试使用作为字段的字符串,并出现相同的错误。 我使用了一个简单的插入注入的字符串(我将做一些测试其他人的工作) 例如:

CALL user_get_by_id(4);
CALL user_get_by_id('4');
CALL user_get_by_id('4; INSERT INTO users () VALUE ();'); (I tested the second statement, and ensured it worked as a regular query, but it did not insert when used this way)

我会从我这边继续测试,但也许这里有人知道我是否 99% 在那里。 谢谢你的帮助!

我应该提到有一个PHP脚本,它将调用该过程并使用用户输入。

这将是类似于...

$query = "CALL user_get_by_id(".mysql_escape_string($+POST[id]).")";

这个问题与提出的类似问题不同的原因:

  1. 提供的响应是制作 SQL 注入安全的 PHP 代码的不同方法,但这个问题是关于另一种使用存储过程的方法,该问题中没有提供。此外,这个问题的答案并不符合我的喜好。否则,当我第一次、第二次或第三次遇到它时,我会使用该线程。
  2. 这个问题不是一般的如何提问——这个问题是有针对性的,专门征求意见。我认为通过将特定问题标记为与一般问题相同来结束它是不合理的(例如如果有人对他们的数学方程式不起作用的原因感到困惑,受访者指出原因是有道理的,而不是将问题标记为已关闭并将他们引导至免费的在线数学课程)
php mysql sql 注入

评论

2赞 Stu 6/8/2023
Sql注入是数据访问层的一个漏洞,它动态构造sql以从用户输入中执行,该过程在此场景中不容易受到攻击;关于SQL注入以及如何在数据访问层中正确使用参数,有很多很好的北极。
6赞 Your Common Sense 6/8/2023
从技术上讲,它是安全的。然而,这并不意味着它应该被用作一种保护措施。
1赞 Petter Pettersson 6/8/2023
你想多了
2赞 Your Common Sense 6/8/2023
在 8.2 中,您不能使用mysql_escape_string。在 8.2 中,您可以编写一行,这甚至无法接近您的存储过程的怪物,因为它“冗长且复杂”。在 PHP 8.2 以下版本中,您可以轻松地编写此函数的替代品。mysqli_execute_query($mysqli, "SELECT id, email, name, password_hash FROM users WHERE id=?", [$_POST['id']])->fetch_assoc();
1赞 Your Common Sense 6/8/2023
注意:使用这种方法,您必须为添加到应用程序的每个查询更改数据库。例如,100 个查询 = 100 个存储过程。当您需要第 101 个时,需要进行另一次架构更改。

答:

1赞 Bill Karwin 6/12/2023 #1

这实际上是安全的,不会有 SQL 注入:

PROCEDURE user_get_by_id (IN user_id INT)
BEGIN
    SELECT id, email, name, password_hash FROM users WHERE id=user_id;
END

通过将输入参数声明为 type ,您尝试作为参数传递的任何字符串都将被强制转换为数值。INT

在MySQL中,像“123abc”这样的字符串的数值是123,它忽略任何后面的非数字字符。没有前导数字的字符串(例如“abc123”)的数值为 0。

因此,即使您这样做也是安全的:

CALL user_get_by_id('4; INSERT INTO users () VALUE ();'); 

过程输入的值只是值 4。参数中的所有其他字符都将被删除,因为参数被强制转换为 .INTINT

因此,使用该过程至少可以有效地防止 SQL 注入。

但是,这不是推荐的解决方案,因为有一些更简单的方法不需要存储过程。

在 PHP 中,你可以很容易地将 POST 参数转换为整数值,并将其作为值传递给过程:

$query = "CALL user_get_by_id({intval($_POST['id'])})"; 

甚至可以直接在查询字符串中使用它:

$query = "SELECT id, email, name, password_hash FROM users WHERE id={intval($_POST['id'])}";

这是对整数 SQL 注入的有效保护,但它不适用于字符串或日期输入。

以下操作是不安全的,因为没有类型转换,因此仍然存在将特殊字符复制到 SQL 查询中并导致意外逻辑的风险:

$query = "SELECT id, email, name, password_hash FROM users WHERE name='{$_POST['id'])}'";

您可以尝试转义特殊字符,但这会让人感到困惑。

建议的解决方案是使用查询参数,因为它始终可靠、更简单且更安全。查询参数不是简单地插值到查询字符串中。它们在分析查询之前将动态值与查询分开,因此输入值无法破坏查询的预期逻辑。

$query = "SELECT id, email, name, password_hash FROM users WHERE id=?";
$stmt = $pdo->prepare($query);
$stmt->execute( [ $_POST['id'] ] );

查询参数同样适用于数字输入和字符串输入。

使用查询参数时,编写代码、读取代码和调试代码变得更加容易!

评论

0赞 Watachiaieto 6/12/2023
感谢您清晰简洁的回答。我很感激!我使用 prep 而不是存储过程有三个原因......1.你是对的,制作存储过程非常繁琐。2. PHP 类似于... $query = “CALL procedure([$_POST['param']])”;,但这是不安全的。");删除用户;--“仍然会导致SQL注入,从而违背其目的。3. 对于准备好的语句,有一个简单的一行解决方案。mysqli_execute_query($mysqli, “SELECT id, email, name, password_hash FROM users WHERE id=?”, [$_POST['id']]),我不使用它会很傻。
1赞 Bill Karwin 6/12/2023
太好了,我很高兴这很有帮助。就其价值而言,我更喜欢 PDO 接口而不是 mysqli,所以我建议您检查一下。自 5.1 版以来,PDO 一直是默认 PHP 配置的一部分,因此没有理由不喜欢它。
1赞 Dharman 8/10/2023 #2

要进行 SQL 注入,您需要 2 种编程语言(或一种自我评估的语言,例如使用 eval)。您显示的存储过程是纯 SQL,因此无法注入任何内容。

但是,如果从 PHP 调用此过程,则可以进行 SQL 注入。像这样的代码很容易受到攻击:

$pdo->query('CALL sp('.$_POST['data'].')');

从 PHP 执行任何 SQL 时,必须使用预准备语句。如果你记得永远不要在SQL中放置任何PHP变量,那么你应该不会被SQL注入。

在 PHP 中,mysqli 和 PDO 这两个扩展都提供预处理语句。确保你这样称呼它:

$stmt = $pdo->prepare('CALL sp(?)');
$stmt->execute([$_POST['data']]);
// or
$mysqli->execute_query('CALL sp(?)', [$_POST['data']]);

现在,由于 SQL 字符串是常量,因此无需 SQL 注入即可。

无关紧要,但我强烈建议不要在 PHP 代码中使用存储过程。它们很难使用且令人讨厌。特别是对于简单的 SQL,如您所展示的那样,存储过程是不合适的。除非绝对必要,否则不要使用存储过程。