EXEC(@sql) 会直接执行拼接后的完整字符串,无法隔离代码与数据,导致SQL注入;唯一安全方案是严格使用sp_executesql三段式:占位符、显式声明参数类型、带等号赋值。

EXEC(@sql) 会把整个字符串当代码执行
SQL Server 的 EXEC 不区分“结构”和“数据”,它拿到什么就解析执行什么。一旦你写 EXEC('SELECT * FROM users WHERE name = ''' + @name + ''''),@name 的值在拼接阶段就被塞进 SQL 字符串里,后续不再做任何语义隔离——哪怕传入的是 'admin'' OR 1=1 --',最终执行的就是一条完整恶意语句。
所有拼接点都是注入入口,不管来源是否“可信”
很多人以为从数据库查出来的字段是安全的,结果在存储过程中用 EXEC 拼接日志、配置或缓存内容时中招。只要变量参与字符串拼接,它就不再是“数据”,而是“代码的一部分”。
- 从
sys.tables查出的表名,若直接拼进@sql,攻击者可提前往系统表注入恶意值 - 用
CONTEXT_INFO存的用户 ID,若未校验就拼进动态 SQL,等于开放后门 - 哪怕只拼一个数字:
CONVERT(NVARCHAR, @id)进字符串,仍是拼接,不是参数化
REPLACE、QUOTENAME、加引号都挡不住 EXEC 拼接
REPLACE(@input, '''', '''''') 或 QUOTENAME(@table) 只是文本转义,不能阻止语法层面的注入。因为 EXEC 执行前不编译、不校验、不分离——它不关心你转义没转义,只管执行整条字符串。
- 攻击者可用
1; DROP TABLE logs; --绕过单引号过滤 - 可用
CHAR(0x27)构造全角或 Unicode 单引号绕过正则 -
QUOTENAME()对表名有用,但对拼进字符串里的值毫无防护意义
真正安全的替代方案只有 sp_executesql 三段式
sp_executesql 是 SQL Server 唯一能实现“编译期结构/运行期数据分离”的机制。但它必须严格满足三个条件:
-
@sql字符串里只允许@param占位符,不能出现任何变量值 - 第二个参数(如
N'@id INT, @name NVARCHAR(50)')必须显式声明类型和长度 - 第三个及之后的参数必须带
@param = value形式,不能省略等号或顺序错位
漏掉任意一点,比如把 @name NVARCHAR(MAX) 当成“更安全”,反而扩大攻击面;或者漏写 @name = N'John' 中的 =,就会导致参数错位、类型隐式转换甚至执行失败。
最常被忽略的其实是“动态对象名”——表名、列名、排序方向这些根本没法参数化,只能靠白名单 + 系统视图校验 + QUOTENAME() 封装,缺一不可。

















