在带有准备好的语句的存储过程中使用 '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 几乎所有其他编程接口都更容易编码。
我正在尝试保护我的存储过程以避免 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 几乎所有其他编程接口都更容易编码。