sp_executesql仅防值注入,动态对象名须用QUOTENAME()安全包装并配合白名单校验;排序方向等非值参数必须严格枚举控制;执行上下文需最小权限,禁用元数据动态拼接与无效过滤。

sp_executesql 不能防止动态列名、表名或排序方向的注入——它只保护值参数。真正安全的动态 SQL 存储过程,必须把「语法结构」和「数据值」彻底分开处理。
用 QUOTENAME() 处理对象名(表名、列名、schema)
表名、列名、排序字段这些属于 T-SQL 语法的一部分,无法用参数占位符替代,硬拼接就等于开门揖盗。QUOTENAME() 是唯一被 SQL Server 官方认可的安全包装函数,它会自动转义并加双方括号,比如 QUOTENAME('user; drop table x --') 返回 [user; drop table x --],让非法字符失去语法意义。
常见错误是只对输入做简单替换(如把单引号替换成两个单引号),这在 unicode 同形字、嵌套注释等绕过手法面前完全失效。
- 只允许传入纯标识符字符串,不带任何空格、点号、括号;否则
QUOTENAME()仍可能被滥用 - 如果列名来自用户选择下拉框,建议用白名单映射(如
@sortcol = 'name'→ 实际用CASE @sortcol WHEN 'name' THEN 'FirstName' END) -
QUOTENAME(@schemaname)+QUOTENAME(@tablename)必须成对出现,缺一不可
排序方向(ASC/DESC)必须白名单校验
直接拼接 @direction 到 ORDER BY ... ' + @direction 是高危操作。SQL Server 不允许参数化排序方向,但也不意味着只能放任拼接。
正确做法是用 CASE 表达式控制输出,把用户输入限制为严格枚举值:
DECLARE @sortdir NVARCHAR(4) = UPPER(@sortdir_input);
IF @sortdir NOT IN ('ASC', 'DESC') SET @sortdir = 'ASC';
-- 然后在动态 SQL 中:
... ORDER BY ' + @sortcol + ' ' + @sortdir注意:@sortdir 本身不能进 sp_executesql 参数列表,但必须在拼接前完成校验,且不允许空格、制表符、unicode 全角字符混入。
避免执行计划污染和权限越权
每次拼接不同字符串,都会生成新执行计划,不仅浪费 CPU,还会掩盖真实注入行为——因为慢查询日志里全是“唯一语句”,看不出攻击模式。
更危险的是权限模型错位:即使调用者只有 SELECT 权限,只要存储过程用 EXECUTE AS OWNER 或以 db_owner 身份运行,注入语句就能以高权限执行。
- 存储过程应使用最小权限上下文,例如
EXECUTE AS 'app_reader',且该用户仅拥有目标表的SELECT - 禁用
CREATE TABLE、DROP、EXEC等 DDL/DCL 权限,哪怕只是临时表 - 避免用
sys.tables或sys.columns动态查元数据后再拼接——这等于把数据库结构暴露给攻击面
别信“过滤单引号”或“替换分号”这类土办法
正则替换 ' → ''、删掉 ;、过滤 UNION 关键字……这些在真实攻击中基本无效。攻击者早就不靠明文分号了,改用 WAITFOR DELAY '0:0:5' 盲注,或用 /*/**/UNION SELECT 绕过关键字检测。
真正能落地的防线只有两条:QUOTENAME() 包裹所有对象名 + 白名单约束所有非值类动态片段(排序方向、join 类型、聚合函数名等)。其他都是幻觉。
最常被忽略的一点:前端传来的 @tablename 可能是 users] ; DROP TABLE logs --,而 QUOTENAME() 对这种已闭合方括号的字符串无能为力——所以必须先做长度截断(如 SUBSTRING(@input, 1, 128))再 QUOTENAME()。

















