在带有准备好的语句的存储过程中使用 'where..in' (Safe Way)

Using 'where..in' inside store procedure with prepared statements (Safe Way)

我正在尝试保护我的存储过程以避免 SQL 使用 prepared

的注入攻击

声明。使用此处提到的指南:

"https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html"

mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> SET @a = 3;
mysql> SET @b = 4;
mysql> EXECUTE stmt1 USING @a, @b;
+------------+
| hypotenuse |
+------------+
|          5 |
+------------+
mysql> DEALLOCATE PREPARE stmt1;

我一个一个传参没问题

现在如果我必须将项目数组从 java 传递给 SP 并使用 'where..in' ,什么是最好的

方法?

我可以用这样的东西:

SET @somestring  = '1,3,18,25';
SET @s=CONCAT("
SELECT * FROM city 
WHERE id IN (",@somestring,");");
PREPARE stmt FROM @s;
EXECUTE stmt;

不知道注入是否足够安全,因为我猜它不检查参数

不使用“USING @a, @b”时一个接一个。

您不能将数组传递给存储过程,因为 MySQL 不支持数组。您的字符串 '1,3,18,25' 是一个恰好包含逗号的字符串。这不是数组。

将未知字符串插入动态SQL语句是SQL注入,句号。您无法确定它不包含会更改动态 SQL 查询语法的特殊字符,因此它不安全。

在动态 SQL 语句中使用变量的最安全方法是使用查询参数。但是有几个问题:我假设你的带有 comma-separated 数字的字符串可能有可变数量的数字,你必须支持它。

查询参数只能用于单个标量值。每个值一个参数:

WHERE id IN (?, ?, ?, ?) 

EXECUTE stmt USING ... 的语法支持可变数量的参数,但不支持 动态 数量的参数。您必须将参数编码为代码中固定的参数,并且参数必须是单独的 user-defined 变量(带有 @ 标志的类型)。没有好的方法可以将一串 comma-separated 值转换为相同数量的单个变量。可以在循环中提取子字符串,但代码量很大。

它仍然无济于事,因为您必须找到一种方法将动态数量的参数传递给 EXECUTE ... USING

MySQL 用户的常见解决方法是使用 FIND_IN_SET()。这使您可以将列与 comma-separated 值字符串相匹配。

WHERE FIND_IN_SET(id, '1,3,18,25') > 0

因此您可以将字符串作为 单个 参数传递给准备好的语句:

SET @somestring  = '1,3,18,25';
SET @s='SELECT * FROM city WHERE FIND_IN_SET(id, ?)';
PREPARE stmt FROM @s;
EXECUTE stmt USING @somestring;

事实上,您甚至不需要为此使用 PREPARE & EXECUTE。您可以直接在查询中使用 MySQL 个变量。

SELECT * FROM city WHERE FIND_IN_SET(id, @somestring);

这是安全的,因为变量不会导致SQL注入。在您创建存储过程时查询已经被解析,因此变量的内容不可能影响查询的语法,这是我们试图避免的。

这是安全的...但未优化。通过使用 FIND_IN_SET(),查询无法使用索引来搜索字符串中的值。它将被迫做一个table-scan。可能不是你想要的。

那么解决方案有哪些选择呢?

您可以检查输入字符串以确保它只有数字和逗号,如果没有则中止。

IF @somestring NOT REGEXP '^([[:digit:]]+,)*[[:digit:]]+$' THEN
  SIGNAL SQLSTATE VALUE '45000'
    SET MESSAGE_TEXT = 'Invalid input, please use only comma-separated integers';
FI

一旦您确认该字符串是安全的,您就可以安全地将它插入到查询字符串中,就像您使用 CONCAT() 的示例一样。

我的首选解决方案是停止使用 MySQL 存储过程。我几乎从不使用它们,因为 MySQL 几乎所有其他编程接口都更容易编码。