必须用sp_executesql,不能用EXEC(@sql):因sp_executesql在编译阶段分离代码与数据,参数值不参与语法解析,可彻底防注入;而EXEC直接执行拼接字符串,无隔离层,极易被绕过。

必须用 sp_executesql,不能用 EXEC(@sql) —— 后者等于把用户输入当代码执行,参数化形同虚设。
SQL Server 里为什么 sp_executesql 是唯一安全选项
SQL Server 在编译阶段就分离代码与数据:@param 的值不参与语法解析,哪怕传入 '1; DROP TABLE users; --',也只会被当作字符串字面量处理。而 EXEC(@sql) 是直接求值执行,中间没有任何隔离层。
-
sp_executesql要求三段式写法:SQL 字符串 + 参数签名字符串 + 实际参数列表 - 参数签名必须显式声明类型和长度,比如
N'@id INT',不能写成N'@id SQL_VARIANT'或漏掉长度(NVARCHAR(50)不是NVARCHAR(MAX)) - 传参必须带
@param = value形式,不能只写@id;顺序错一位,类型和值就会错位
表名、列名这些“语法成分”怎么拼才不炸
它们无法参数化,硬拼就是高危操作。白名单校验不是可选动作,是强制步骤。
- 禁止对表名做
REPLACE(@table, '''', '''''')或QUOTENAME()后直接拼进@sql——QUOTENAME()只用于拼接,不代替校验 - 先查系统视图确认存在且合法:
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = @table_name AND schema_id = SCHEMA_ID('dbo')) RETURN - 或硬编码白名单:
IF @sort_col NOT IN ('created_at', 'status', 'amount') THROW 50000, 'Invalid sort column', 1; - 拼接时再套
QUOTENAME(@table_name),仅此一处可用
参数声明和输入校验漏一步就前功尽弃
用了 sp_executesql 不代表自动免疫。宽泛类型、缺失长度检查、隐式转换,都会打开攻击面。
- 数字类参数声明为具体类型(
INT、TINYINT),开头加硬约束:IF @user_id 999999 RETURN - 字符串立即检查长度和内容:
IF LEN(@name) = 0 OR LEN(@name) > 50 OR @name LIKE '%[^a-zA-Z0-9_]%' RETURN - 禁止在过程内对输入做
CAST/CONVERT—— 转换失败报错,转换成功可能已失真(比如把'1 OR 1=1'隐式转成1)
最容易被忽略的是隐性拼接点:比如用 CONTEXT_INFO 存用户 ID 后在触发器里拼 SQL,或者用 OPENROWSET 构造远程查询字符串——这些地方没走 sp_executesql,也没做白名单,漏洞照出。

















