动态拼接SQL字符串本身不安全,唯一真正防注入的方式是用sp_executesql参数化执行,且参数必须为纯值、禁止任何拼接;表名列名等结构需白名单校验,QUOTENAME()仅作最后转义。

动态拼接 SQL 字符串本身不安全,sp_executesql 不是“加固手段”,而是唯一能真正切断注入链路的执行方式——前提是不用它来拼接,只用它来参数化。
为什么 EXEC(@sql) 拼接字符串等于主动开门
只要把用户输入直接塞进 @sql 字符串里,无论加没加单引号、有没有 REPLACE(@input, '''', ''''''),都拦不住绕过。攻击者输入 admin''; DROP TABLE users--,拼出来就是合法语法的两条语句。
-
EXEC(@sql)把整段字符串当 T-SQL 编译执行,数据库不区分哪部分是结构、哪部分是数据 - 引号转义只是文本替换,不是语义隔离;SQL Server 解析时早就不看“转义”这回事了
- 哪怕只拼一个
WHERE条件,漏判NULL或空字符串,就可能让恶意片段逃逸到语句末尾
sp_executesql 的正确用法:参数必须是纯值,不能是拼出来的字符串
sp_executesql 防注入的前提,是它的参数列表(第二个参数)和值列表(第三个及之后)里,**不出现任何字符串拼接结果**。常见错误写法:N'@p1 nvarchar(50) = ''' + @user_input + ''' ' —— 这又回到拼接老路,@p1 声明本身就被污染了。
- 声明参数类型用静态 Unicode 字符串:
N'@name nvarchar(100), @age int' - 传参用变量原值:
@name = @inputName, @age = @inputAge,不加引号、不拼 N 前缀 -
@stmt里只出现@name这类占位符,绝不出现''' + @inputName + ''' - 示例正确写法:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Users WHERE name = @name AND status = @status';<br>EXEC sp_executesql @sql,<br> N'@name nvarchar(100), @status char(1)',<br> @name = @inputName,<br> @status = @inputStatus;
表名、列名、排序字段这些非参数内容怎么处理
参数化对标识符无效,@tablename 不能直接放进 FROM @tablename。这类内容必须走白名单或系统视图校验,QUOTENAME() 只是最后一步转义,不能替代前置判断。
- 先查
sys.tables确认存在且属预期 schema:IF NOT EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE s.name = 'dbo' AND t.name = @table_name) - 禁止用
OBJECT_ID(@table_name)单独判断——它不校验 schema,'malicious; DROP TABLE x--'可能被截断后误判 - 白名单优先用
CASE WHEN @sort_col IN ('created_at', 'status') THEN @sort_col ELSE 'id' END,再套QUOTENAME() - 所有动态拼接点都要检查:
ORDER BY、GROUP BY、临时表名、CONTEXT_INFO读取后构造的 SQL、OPENROWSET连接字符串
最容易被忽略的是:参数化只保住了“值”的安全,但一旦你把用户输入用于生成结构(哪怕只是加个 ASC/DESC),就立刻跳出参数化保护范围。每个拼接动作都得单独过白名单或存在性校验,没有捷径。

















