我有一个简单的存储过程:
DELIMITER $$
CREATE PROCEDURE `findUserByName`(IN `name` VARCHAR(36))
BEGIN
set @name = name;
PREPARE findStmt FROM 'SELECT * FROM user WHERE name=?';
EXECUTE findStmt USING @name;
END$$
我在 PHP 中使用prepare 方法调用它:
$stmt = $mysqli->prepare('CALL findUserByName(?)');
$stmt->execute([$inputName]);
我对上面的代码有两个疑问:
这个赋值语句
set @name = name;
显得有些多余,我可以直接使用参数吗name
?MySQL不允许我使用USING name
,我也尝试过将参数名称改为@name
,但不会被识别。既然我在PHP中使用了prepare方法,那么将参数与SQL连接起来是否安全?
set @sql = CONCAT('SELECT * FROM user WHERE name=''', name, ''''); PREPARE findStmt FROM @sql; EXECUTE findStmt;
为过程参数选择一个不同的名称,避免与列名称重复。然后您可以直接在查询中使用该参数:
EXECUTE
.@
我同意这很奇怪且令人不满意,因为您必须设置一个看似多余的用户定义变量(带有印记的类型)。https://dev.mysql.com/doc/refman/8.0/en/execute.html说:
不,如果字符串可能包含不受信任的内容,则将字符串连接到动态 SQL 中是不安全的。这称为 SQL 注入,它是错误或数据泄露的常见来源。
在上面的示例中,如果有人输入自己的名字“O'Reilly”,会发生什么?
您可能会看到“使用准备好的语句”来保护您的代码免受 SQL 注入的建议,但仅仅使用
PREPARE
并不神奇。它不会祝福不安全的 SQL 使其变得安全。建议应该是“使用参数”。使用参数是必要的,但保证安全的重要部分PREPARE
是参数,而不是。PREPARE
回复您的评论:
如果您的查询在创建过程时是固定的,并且与之组合的变量仅替换标量值,那么您不需要使用
PREPARE/EXECUTE
.如果查询的语法部分基于过程的逻辑,则必须使用动态 SQL。也就是说,您不仅需要以变量的形式将标量值与查询组合起来,还需要修改查询本身,例如修改表达式或关键字 - 除了简单标量值之外的任何内容。
您可能也对我在这里的答案感兴趣:通过 MySQL 连接器在存储过程中进行 SQL 注入
PREPARE/EXECUTE
我写这篇文章是为了进一步解释关于何时解析查询的本质。为了了解 SQL 注入的风险,了解这一点非常重要。