存储过程参数本身安全,但拼接进exec即失效;必须用sp_executesql或prepare+using进行参数化绑定,且仅对数据值有效,表名列名等标识符须白名单校验,并配合最小权限与错误信息管控。

存储过程参数本身是安全的,但拼接进 EXEC 就失效了
很多人以为只要用了 CREATE PROCEDURE,参数自动就被“隔离”了。其实不然:@username 这类输入参数在直接用于静态 SQL(如 WHERE name = @username)时确实是安全的;但一旦你把它拿来拼字符串,比如 SET @sql = 'SELECT * FROM users WHERE name = ''' + @username + '''',再交给 EXEC(@sql) 执行,那它就彻底退化成普通文本——单引号、分号、-- 全部照单全收。
常见错误场景:
- 把排序字段名(
@sortColumn)直接拼进ORDER BY子句 - 用用户输入构造表名或列名,再塞进
SELECT * FROM ' + @tableName - 在 MySQL 里写
SET @sql = CONCAT('SELECT ... WHERE id = ', in_id),没加引号也没参数化
sp_executesql 和 PREPARE 不是防注入开关,而是工具
sp_executesql(SQL Server)和 PREPARE+EXECUTE(MySQL)本身不防注入,它们只提供参数绑定能力——但必须显式使用,且仅对值有效。
容易踩的坑:
-
sp_executesql N'SELECT * FROM users WHERE name = @name', N'@name NVARCHAR(50)', @name = @input✅ 正确:参数绑定到位 -
sp_executesql N'SELECT * FROM users WHERE name = ''' + @input + ''''❌ 错误:根本没用上参数机制 - MySQL 中写
PREPARE stmt FROM CONCAT('SELECT * FROM ', in_table, ' WHERE id = ?')—— 表名拼接仍危险,?只能代入值,不能代入标识符
白名单校验比类型声明更重要
即使你声明了 @action VARCHAR(10),SQL Server 也不会拦截 'DROP TABLE;--' 这种输入。长度和类型约束 ≠ 内容过滤。
真正起作用的是主动控制:
- 排序字段只允许
IN ('id', 'created_at', 'status'),其余一律SIGNAL报错 - 动态表名必须查配置表或硬编码列表,不能来自任意输入
- LIKE 查询的通配符(
%)必须由存储过程内部包裹,比如SET @pattern = CONCAT('%', @keyword, '%'),再传给USING或sp_executesql
权限和错误信息暴露会绕过所有代码防护
哪怕存储过程里每一行都写得滴水不漏,如果它用的是 sa 权限账号,或者开启 ARITHABORT ON 导致报错泄露表结构,攻击者就能靠错误型注入或盲注逐步拖库。
关键检查点:
- 执行该存储过程的数据库账号,是否只具备
SELECT某几张表的权限? - 生产环境是否关闭详细错误(如 SQL Server 的
SHOWPLAN、MySQL 的general_log)? - 日志记录的是调用参数(
@username = 'admin'),还是拼出来的完整 SQL(SELECT * FROM users WHERE name = 'admin'; DROP TABLE...)?后者等于留作案证据
真正难防的,不是语法怎么写,而是开发者看到“已封装进存储过程”就松一口气,却忘了翻一遍里面有没有 SET @sql = ... + @user_input 这样一行代码。注入不在定义时发生,而在 EXEC 或 EXECUTE 落地那一刻生效。











