必须用sp_executesql,禁用exec(@sql);其三段式(@stmt、@params、参数值)须严格匹配,表名列名须白名单校验后quotename拼接,输入须前置强校验。

必须用 sp_executesql,禁用 EXEC(@sql) —— 后者不隔离数据与代码,拼进去就执行,防不住任何注入。
sp_executesql 的三段式写法不能错位
它不是“带参数的 EXEC”,而是编译期就分离代码和数据的机制。错一个位置,参数就变成拼接字符串。
-
@stmt字符串里只能出现@param_name占位符,不能出现任何变量值或表达式(比如N'SELECT * FROM t WHERE id = ' + @id是错的) -
@params必须是 Unicode 字符串,显式声明类型和精度,例如N'@id INT, @name NVARCHAR(50)';写成N'@id SQL_VARIANT'或漏掉长度会触发隐式转换,可能截断或失败 - 传参必须带
@param = value形式,例如@id = @user_id, @name = @input_name;只写@user_id, @input_name会导致顺序错乱,类型和值错配
表名、列名这些标识符不能参数化,但也不能裸拼
你没法写 FROM @table,SQL Server 语法直接报错。硬拼又等于开门揖盗——QUOTENAME() 只是转义,不是校验。
- 白名单校验是强制步骤:用
IF @table_name NOT IN ('orders', 'users', 'products')或查sys.tables确认存在且归属预期 schema - 确认通过后,再用
QUOTENAME(@table_name)拼进@stmt,仅此一处可用 - 禁止把
QUOTENAME()当过滤器用:比如QUOTENAME(@user_input)传给sp_executesql做值参数,这是典型误用——值该走参数绑定,不该进字符串
参数输入校验必须在 sp_executesql 之前做
用了 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,或者日志语句里拼接未过滤的字段值——这些地方没走 sp_executesql,也没做白名单,漏洞照出。










