我对存储过程以及如何使用它们来防止 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
我运行过的一些测试... 它适用于整数的 id... 它适用于作为字符串的整数的 id... 如果我使用字符串,它会说明有关字段的信息 - 但我只是希望它识别它不是 int?我尝试使用作为字段的字符串,但得到了相同的错误。 我使用了一个带有简单插入注入的字符串(我将做测试其他的工作) EG
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 注入:
PROCEDURE user_get_by_id (IN user_id INT)
BEGIN
SELECT id, email, name, password_hash FROM users WHERE id=user_id;
END
通过将输入参数声明为类型
INT
,您尝试作为参数传递的任何字符串都将被强制转换为数值。
在 MySQL 中,像“123abc”这样的字符串的数值是 123,它会忽略后面的所有非数字字符。没有前导数字的字符串(例如“abc123”)的数值为 0。
所以即使你这样做也是安全的:
CALL user_get_by_id('4; INSERT INTO users () VALUE ();');
过程的输入值只是
INT
值 4。当参数被转换为 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'] ] );
查询参数对于数字输入和字符串输入同样有效。使用查询参数,编写代码、阅读代码、调试代码更加容易!
但是,如果您从 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,存储过程是不合适的。除非绝对必要,否则不要使用存储过程。