存储过程本身不防sql注入,安全取决于写法:sql server必须用sp_executesql并严格对齐参数声明与值,mysql必须走prepare→execute→using闭环,所有用户输入只进using或经白名单校验后拼接。

存储过程本身不防SQL注入,安全与否完全取决于你怎么写它。用错语法、拼错字符串、绕过参数化,漏洞照出。
sp_executesql 参数声明和值传入必须严格对齐
SQL Server 里只有 sp_executesql 是可信赖的参数化执行方式。EXEC(@sql) 等价于裸奔,直接拼接字符串就等于给攻击者开后门。
- 第二个参数是参数签名(如
N'@id INT'),类型必须显式声明,不能写SQL_VARIANT或漏掉长度(VARCHAR(50)不要写成VARCHAR(MAX)) - 第三个及之后的参数才是实际值,顺序错一位就会把类型当值、把值当类型,导致静默失败或类型转换异常
- 字符串类参数建议加长度限制和内容过滤:
IF LEN(@name) > 50 OR @name LIKE '%[^a-zA-Z0-9_]%' THROW 50000, 'Invalid name format', 1;
表名、列名、排序方向等标识符无法参数化,必须白名单校验
参数化只保护值(value),不保护语法结构(syntax construct)。@sortcolumn 传 'name asc; drop table users; --',拼进 ORDER BY @sortcolumn 就直接执行非法语句。
- 表名列名必须用
QUOTENAME()包裹,但仅靠它不够——QUOTENAME('users; drop table logs')仍会生成合法但危险的[users; drop table logs] - 真正安全的做法是查
INFORMATION_SCHEMA.TABLES或sys.tables白名单,再用QUOTENAME()输出;排序方向(ASC/DESC)必须用CASE枚举,不能直拼 - 错误示例:
'ORDER BY ' + @sortcol + ' ' + @sortdir;正确写法:CASE @sortdir WHEN 'ASC' THEN 'ORDER BY ' + QUOTENAME(@sortcol) + ' ASC' ELSE 'ORDER BY ' + QUOTENAME(@sortcol) + ' DESC' END
MySQL 存储过程里 PREPARE→EXECUTE→USING 必须闭环使用
MySQL 动态 SQL 安全链只有 PREPARE→EXECUTE→USING 这一条路。用户输入只能进 USING,绝不能进 CONCAT() 构建的语句体。
-
USING后面必须是用户变量(@in_name),不是存储过程参数名(in_name),否则报错或静默失败 - 危险写法:
SET @sql = CONCAT('SELECT * FROM users WHERE name = ''', in_name, '''');—— 单引号一闭合,in_name传' OR 1=1 --就穿透 - 安全写法:
SET @sql = 'SELECT * FROM users WHERE name = ?'; PREPARE stmt FROM @sql; EXECUTE stmt USING @in_name; - 若需拼表名,必须先查
INFORMATION_SCHEMA.TABLES校验是否存在且属于允许范围,再用CONCAT()拼接
权限模型错位比语法漏洞更致命
即使你所有字符串都用了 QUOTENAME() 和 sp_executesql,如果存储过程以 db_owner 身份执行,而调用者只有 SELECT 权限,注入语句仍以高权限上下文运行。
- 存储过程应以最小必要权限创建,避免
WITH EXECUTE AS OWNER;优先用EXECUTE AS CALLER,让权限继承调用者上下文 - 动态 SQL 中涉及的表、视图、函数,其权限必须显式授予调用者,不能依赖存储过程所有者的隐式权限
- 盲注(如
WAITFOR DELAY '0:0:5')不依赖错误回显,仅靠时间差就能探测逻辑漏洞,这类攻击在高权限上下文中更难被日志捕获
最常被忽略的点:动态 SQL 的执行计划缓存污染。每次拼接不同字符串,都会生成新执行计划,不仅拖慢性能,还掩盖注入痕迹——因为 SQL Server 不会把 SELECT * FROM users WHERE id = 1 和 SELECT * FROM users WHERE id = 2 当作同一条语句缓存。











