提问人:BooRuleDie 提问时间:11/17/2023 更新时间:11/18/2023 访问量:66
通过 MySQL 连接器在存储过程中注入 SQL
SQL Injection in Stored Procedures via MySQL Connector
问:
我使用驱动程序来执行数据库操作。最近,我在MySQL中遇到了存储过程,并决定将我的一些API从utiling迁移到。事实证明,这种转变是成功的,一切都在无缝衔接。但是,我不确定这些存储过程对 SQL 注入的脆弱性。mysql-connector-python
cursor.execute()
cursor.callproc(proc_name, args=())
为了评估这一点,我创建了一个测试存储过程,并检查了它对 SQL 注入有效负载的敏感性。
存储过程:
DELIMITER //
CREATE PROCEDURE TestProcedure(IN arg_test VARCHAR(150))
BEGIN
IF EXISTS (SELECT 1 FROM Random_Table WHERE test = arg_test) THEN
SELECT 'success' AS message;
ELSE
SELECT 'failed' AS message;
END IF;
END //
DELIMITER ;
负载:
' or 1=1 --
" or 1=1 --
令人惊讶的是,这些有效载荷都没有产生任何成功的结果。为了寻求进一步的保证,我咨询了公司的一位数据库管理员。尽管他表示不确定,但他认为这些存储过程的功能与预准备语句类似。这与我的观察结果一致,即该方法通过参数接受用户输入作为参数,类似于预准备语句。cursor.callproc()
args
如果这种解释是准确的,则意味着使用该方法可以确保后端的安全,而没有SQL注入问题。尽管有这些积极的迹象,但我想在这里寻求进一步的确认,以保证这种方法的安全性。cursor.callproc()
答:
就像经常发生的那样,问题出在术语上。它造成了很多麻烦(和漏洞)。程序员应该始终严格定义。
您在这里谈论的是使用存储过程的参数。此类参数是与存储过程相关的特例,在性质上与 SQL 变量非常相似。
在这种情况下,您的数据库不会将 的内容添加到 SQL 正文中(就像在 python 中所做的那样),然后评估生成的 SQL。相反,是用法,就好像它是一个变量(从这个意义上说,它确实类似于准备好的语句,但从技术上讲,它是完全不同的事情)。arg_test
+ arg_test +
arg_test
在谈到存储过程时,它们与注入无关。就其本身而言,仅使用程序并不能保证任何事情。一个人可以写一个对注射免疫的程序,也可以写另一个容易注射的程序。就像任何其他代码一样。
但是,只要您只是使用传递给过程的参数,它就是安全的。
如果这种解释是准确的,则意味着使用 cursor.callproc() 方法可以确保后端安全,没有 SQL 注入问题。
仅仅使用本身并不能确保该过程没有 SQL 注入风险。这取决于过程正文中的代码。cursor.callproc()
SQL 注入漏洞是在分析查询之前将不受信任的有效负载引入查询时导致的,因此该有效负载中的内容可能会影响查询的语法。
但是,使用过程参数不会将参数的值插入到 SQL 语法中。从语法上讲,它的工作方式与使用字符串文字完全相同,如下所示:
IF EXISTS (SELECT 1 FROM Random_Table WHERE test = 'my test') THEN
...
使用参数代替字符串文本不会影响查询的语法。创建过程时,语法是固定的。该参数只能表现为单个标量值。
存储过程中的局部变量和用户定义变量也是如此。
是否可以设计具有 SQL 注入漏洞的存储过程?是的,这是可能的,但您的程序没有这样做。
您必须将新查询编写为字符串,将参数插入到该字符串中,然后使用 PREPARE 和 EXECUTE 将字符串作为查询运行。这样,语法分析将在合并不受信任的有效负载后进行,并且有效负载可能会影响查询的语法。在字符串通过串联形成后,MySQL不知道查询中的哪些字符是硬编码查询的一部分,以及哪些字符来自参数。
不安全查询的示例可能如下所示:
CREATE PROCEDURE TestProcedure(IN arg_test VARCHAR(150))
BEGIN
SET @sql = CONCAT('SELECT 1 INTO @result FROM Random_table WHERE test = ''',
arg_test, '''');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
IF @result = 1 THEN
...
当然,这是一个人为的例子。我把它做成类似于你的程序作为演示。
在某些情况下,在过程中使用 PREPARE 和 EXECUTE 是必要的,在这些情况下,我们不应该使用字符串连接将变量添加到查询中。我们可以将参数传递给 EXECUTE。由于历史原因,它们必须是用户定义的变量,而不是局部变量或过程参数变量,因此我们只需要将有效负载复制到用户定义的变量即可。
CREATE PROCEDURE TestProcedure(IN arg_test VARCHAR(150))
BEGIN
SET @sql = 'SELECT 1 INTO @result FROM Random_table WHERE test = ?';
PREPARE stmt FROM @sql;
SET @param = arg_test;
EXECUTE stmt USING @param;
DEALLOCATE PREPARE stmt;
IF @result = 1 THEN
...
占位符是一个固定的语法元素。无论变量的内容是什么,它都仅表现为单个标量值。在变量与查询组合之前,在 PREPARE 期间分析查询。准备好的查询指出有一个占位符,每个占位符只能是一个标量值。因此,在 EXECUTE 期间传递给查询的变量无法更改查询语法。它只适合在执行过程中的最后一刻为它保留的位置。?
@param
了解查询准备和执行之间的这种差异对于识别 SQL 注入风险至关重要。
评论
SELECT 1 from Users where Password=' + pass + "'"
' OR 1=1 #
SELECT 1 from Users where Password ='' OR 1=1 #