提问人:Watachiaieto 提问时间:6/8/2023 最后编辑:DharmanWatachiaieto 更新时间:8/10/2023 访问量:124
从 PHP 代码调用存储过程会阻止 SQL 注入吗?
Does calling a stored procedure from PHP code prevent SQL injection?
问:
我对存储过程以及如何使用它们来防止 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]).")";
这个问题与提出的类似问题不同的原因:
- 提供的响应是制作 SQL 注入安全的 PHP 代码的不同方法,但这个问题是关于另一种使用存储过程的方法,该问题中没有提供。此外,这个问题的答案并不符合我的喜好。否则,当我第一次、第二次或第三次遇到它时,我会使用该线程。
- 这个问题不是一般的如何提问——这个问题是有针对性的,专门征求意见。我认为通过将特定问题标记为与一般问题相同来结束它是不合理的(例如如果有人对他们的数学方程式不起作用的原因感到困惑,受访者指出原因是有道理的,而不是将问题标记为已关闭并将他们引导至免费的在线数学课程)
答:
这实际上是安全的,不会有 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。参数中的所有其他字符都将被删除,因为参数被强制转换为 .INT
INT
因此,使用该过程至少可以有效地防止 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'] ] );
查询参数同样适用于数字输入和字符串输入。
使用查询参数时,编写代码、读取代码和调试代码变得更加容易!
评论
要进行 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,如您所展示的那样,存储过程是不合适的。除非绝对必要,否则不要使用存储过程。
评论
mysqli_execute_query($mysqli, "SELECT id, email, name, password_hash FROM users WHERE id=?", [$_POST['id']])->fetch_assoc();