MySQL存储过程禁用CONCAT拼接用户输入,因无法参数化防护SQL注入;数据值须用PREPARE+EXECUTE配合?占位符,表名列名等标识符必须白名单校验。

MySQL存储过程里用CONCAT拼接用户输入,就是直接开库门——再怎么TRIM或REPLACE都拦不住' OR 1=1 --这类攻击。真正安全的做法是:数据值走PREPARE + EXECUTE配合?占位符;表名、列名等标识符必须白名单校验,不能参数化。
为什么CONCAT拼接用户输入必然高危
存储过程中SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', in_name, '''');这类写法,字符串拼接发生在SQL解析之前,MySQL根本没机会做参数绑定。传入in_name = "admin' OR '1'='1",最终执行的就是SELECT * FROM users WHERE name = 'admin' OR '1'='1'——整张表被拖走。
常见错误现象:
-
in_name加了TRIM()或REPLACE(in_name, "'", "''"),仍被16进制编码或宽字节注入绕过 - 用
USER()或VERSION()发现数据库返回了敏感信息,说明错误回显已暴露结构 - 执行后意外删表、改密码、导出
information_schema,基本可判定已中招
PREPARE + EXECUTE只对数据值有效,别乱塞标识符
?占位符在MySQL中**仅支持数据值**,不支持表名、列名、排序字段、函数名等语法成分。试图写SELECT * FROM ?会直接报错:ERROR 1064 (42000)。
安全可用的场景(用?):
WHERE id = ?INSERT INTO t(col1, col2) VALUES(?, ?)UPDATE t SET status = ? WHERE user_id = ?-
LIMIT ?, 10(第一个?合法,但必须是整数且非负)
必须拦截的场景(禁用?,改用白名单):
-
FROM ?→ 改为IF in_table NOT IN ('users', 'orders') THEN SIGNAL SQLSTATE '45000' ... -
ORDER BY ?→ 映射到固定字段:CASE in_sort WHEN 'created_at' THEN created_at WHEN 'status' THEN status END -
LIMIT 10, ?→ 第二个参数不能是?,需提前校验范围并转为整数
USING子句传参必须用用户变量,不能直传存储过程参数
EXECUTE stmt USING @var1, @var2;里的@var1必须是会话级用户变量,不是存储过程的IN参数。直接写USING in_name会报错:ERROR 1318 (42000)。
正确做法:
- 先显式赋值:
SET @name_val = in_name; - 再执行:
EXECUTE stmt USING @name_val; - 注意类型陷阱:
@name_val若来自INT参数却赋给VARCHAR字段,可能触发隐式转换导致索引失效 - 变量生命周期:每次
EXECUTE前必须确保@var已存在且有值,否则查不到数据也不报错
白名单校验要落在实处,别信“正则够用”
对表名、列名做白名单,不能只靠REGEXP '^[a-z_][a-z0-9_]{1,63}$'这种宽松规则——它放行user_info_backup,但你实际只允许users和orders。
实操建议:
- 硬编码白名单:
IF in_table NOT IN ('users', 'products', 'logs') THEN ... - 查配置表:
SELECT COUNT(*) FROM allowed_tables WHERE table_name = in_table;,命中才继续 - 禁止任何“动态生成列名”的需求,如
ORDER BY in_col,必须拆成多个固定分支 - 分表场景下,表名由日期/哈希决定,也得走映射逻辑,而不是拼接
log_202605
最容易被忽略的一点:白名单检查必须在PREPARE之前完成,且失败时用SIGNAL中断流程,不能只SELECT 'invalid'然后继续往下跑。











