存储过程并非sql注入免疫盾,其安全性取决于编写、调用与授权方式;内部拼接动态sql(如concat、exec)、未校验标识符、调用端字符串拼接、高权限上下文及二次注入等均会导致漏洞。

存储过程本身不是SQL注入的“免疫盾”,它只是一段预编译的数据库代码容器——安全与否,取决于你怎么写、怎么调、怎么授权。
存储过程内部拼接动态SQL仍会中招
只要在存储过程中用了 CONCAT、+ 或 EXEC 拼接用户输入,就等于把解析权交给了攻击者。MySQL 的 PREPARE/EXECUTE 机制只对 ? 占位符绑定的数据值做参数化,对表名、列名、ORDER BY 字段等标识符完全不保护。
- 错误示例:
SET @sql = CONCAT('SELECT * FROM ', in_table, ' WHERE name = ''', in_name, '''');——in_table和in_name都可被注入 - 正确做法:表名列名必须走白名单校验(
IF in_table NOT IN ('users', 'orders') THEN SIGNAL ...),数据值才交给USING绑定 - 注意:
USING只接受用户变量(如@name),不能直接用存储过程参数(in_name)——否则报错ERROR 1318
调用端没参数化,存储过程形同虚设
哪怕存储过程定义了 @user NVARCHAR(50),如果应用层用字符串拼接方式调用,比如 C# 中写 "EXEC login '" + input + "'",那整个参数声明在抵达数据库前就被绕过了。
- 常见失守场景:
mysqli_query("CALL sp_search('$kw')")(PHP)、Statement.execute("CALL search('" + kw + "')")(Java)、MyBatis 的${}语法 - 必须改用真正绑定的方式:
PreparedStatement.setXXX()、SqlCommand.Parameters.Add() - ORM 不等于自动安全——有些框架在分页、排序、多表动态 JOIN 场景下仍会退化为字符串拼接
权限和执行上下文放大风险
存储过程常被授予高权限(如 db_datawriter、EXECUTE AS OWNER),一旦被注入利用,后果比普通查询严重得多:删表、读取系统视图、甚至调用 xp_cmdshell(SQL Server)。
-
EXECUTE AS CALLER比OWNER更可控,但前提是调用账号本身权限最小化 - 云环境(Azure SQL、RDS)通常禁用
xp_cmdshell、OPENROWSET等高危功能,别指望本地能跑通的逻辑在生产也安全 - 避免在存储过程中启用
UNSAFE ASSEMBLY或跨 schema 的 EXECUTE 权限——这些配置本身就是攻击面
最易被忽略的一点:二次注入。用户输入看似“安全”地存进了字段(比如昵称、地址),后续某个报表存储过程又把它拿出来拼进动态SQL,这时连调用端和存储过程本体都“看起来没问题”,但链路已断裂。防御必须贯穿输入、存储、再使用全周期。










