通过限制可用字符和使用 REGEXP() 来防止 MySQL 中的 SQL 注入

Preventing SQL Injection in MySQL by limiting usable characters and using REGEXP()

提问人:CodeMonkeyMajor 提问时间:7/30/2021 更新时间:8/13/2021 访问量:328

问:

我正在寻找一种在不使用预准备语句的情况下防止 SQL 注入的方法。我知道预处理语句是防止SQL注入的正确方法,但是MySQL不允许在ALTER USER预处理语句中使用参数,因此我需要另一种选择:

# Will not work
CREATE DEFINER=`root`@`localhost` PROCEDURE `TestProcedure`()
BEGIN
    SET @InputVal = 'NewPassword';
    SET @SQLStr = 'ALTER USER \'SomeUser\'@\'localhost\' IDENTIFIED BY ?';
    
    PREPARE SQLStatement FROM @SQLStr;
    EXECUTE SQLStatement USING @InputVal;
    DEALLOCATE PREPARE SQLStatement;
END

具体目标是创建一个允许用户更改其密码的存储过程。我假设字母数字输入不会导致 SQL 注入(大概是一个足够公平的假设,但我愿意纠正,因为我已经看到一些非常有创意的 SQL 注入使用退格字符之类的东西):

CREATE DEFINER=`root`@`localhost` PROCEDURE `ChangeCurrentUserPassword`(IN NewPassword VARCHAR(30))
BEGIN
    SET @NewPassword = NewPassword;
    
    IF @NewPassword REGEXP '^[a-z0-9]{1,}$' THEN
        SET @SQLStr = CONCAT('ALTER USER \'', Substring_Index(USER(),'@',1), '\'@\'localhost\' IDENTIFIED BY \'', NewPassword, '\';');
        
        PREPARE SQLStatement FROM @SQLStr;
        EXECUTE SQLStatement;
        DEALLOCATE PREPARE SQLStatement;
    ELSE
        # Non-alphanumeric (or blank) password.
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Password must be alpha-numeric';
    END IF;
END

上面的内容确实按预期工作,并且可能是安全的(除了它允许单字符密码的事实 - 它只是为了测试逻辑而拼凑起来),但我不喜欢它将密码限制为仅字母数字的事实。我希望尽可能多地使用非字母数字字符,以便我可以将其添加到 REGEXP 语句中。

我看到很多其他线程询问使用 REGEXP 来检测 SQL 注入,但对于每个线程,他们都在询问如何从任何原始输入进行检测。这里的关键区别在于,我不是试图检测 SQL 注入尝试,而是尝试限制允许的字符,以便首先无法进行 SQL 注入。

有谁知道可以包含但仍能安全地阻止 SQL 注入的字符列表?

旁注:如果有更好的方法仍然允许手术,我很乐意采用全新的方法来处理此程序。我知道我可以直接更新 mysql.user 表,这将允许正确使用准备好的语句,但我想避免直接更新系统表 - 我觉得像 ALTER USER 这样的语句存在纯粹是因为应尽可能避免直接更新。

对不起,这太啰嗦了。感谢您抽出宝贵时间走到这一步!

MySQL 正则表达式 存储过程 SQL注入

评论


答:

1赞 Booboo 8/13/2021 #1

阅读绕过 mysql_real_escape_string() 的 SQL 注入的答案,尤其是这个

这是一个引用已弃用的PHP函数的旧答案。这已被替换为 ,它对以下特殊字符进行编码:mysql_real_escape_stringmysqli_real_escape_string

NUL (ASCII 0)、\n、\r、\、'、“ 和 Control-Z

其中是换行符 (ASCII x'0A'),是“回车”字符 (ASCII x'0D')。\n\r

mysqli_set_charset($link, 'utf8');
$s = mysqli_real_escape_string($link, chr(0) . "\n\r\\'" . '"' . chr(26));
var_dump($s);

指纹:

string(14) "\0\n\r\\\'\"\Z"

所以:

  1. 确保服务器连接使用字符集 use 或 .utf8mb4utf8
  2. 如上所示,要么转义这 7 个特殊字符,要么不允许在密码中使用部分或全部字符。
  3. 确保转义密码两边有单引号。

因此,例如,如果您只允许字母数字 [A-Za-z] 加上来自“@#$%&!_”的特殊字符,则不需要转义。如果您还允许使用单引号和/或双引号,那么您只需要确保这些引号用反斜杠转义即可。

评论

0赞 CodeMonkeyMajor 8/19/2021
谢谢你的@Booboo。我正在使用 Charset utf8mb4,有效载荷被引号括起来。基于上述情况,一些额外的字符可能也是安全的:£^*()-+={}[]~<>,./?