
SQL Server 存储过程中 EXEC 动态拼接字符串时为什么总被注入?
因为 EXEC(或 sp_executesql)执行的是纯字符串,变量一拼进去,就等于把用户输入直接喂给了 SQL 引擎。哪怕加了 REPLACE(@input, ””, ”””) 这种“手动转义”,也挡不住绕过——比如用注释符 — 截断后续校验逻辑,或用 CHAR(0x27) 绕过单引号检测。
真正有效的做法只有一条:不拼接,改用参数化。
所有外部输入必须作为参数传入 sp_executesql,不能出现在 SQL 字符串里EXEC(@sql) 形式一律禁用;sp_executesql 是唯一安全入口表名、列名等无法参数化的部分,只能白名单校验(如 CASE WHEN @col IN (‘name’, ’email’) THEN @col ELSE THROW 50000, ‘Invalid column’, 1 END)
如何用 sp_executesql 正确传递参数?
关键不是“有没有参数”,而是“参数是否在 SQL 字符串外定义、并在执行时绑定”。很多人写成这样:
DECLARE @sql NVARCHAR(MAX) = N’SELECT * FROM users WHERE id = ‘ + CAST(@id AS NVARCHAR(10)); EXEC sp_executesql @sql;
这仍是拼接,@id 已经被提前转成字符串塞进去了,毫无防护意义。
正确写法是:
DECLARE @sql NVARCHAR(MAX) = N’SELECT * FROM users WHERE id = @id AND status = @status’;<br>EXEC sp_executesql @sql,<br> N’@id INT, @status NVARCHAR(20)’,<br> @id = @id,<br> @status = @status;@sql 字符串里只出现参数占位符(@id, @status),不出现任何变量值第二个参数是参数签名,必须显式声明类型和长度(NVARCHAR(20) 不能简写为 NVARCHAR)第三个及之后的参数是实际值绑定,顺序/名称需与签名一致
哪些地方看似安全,其实仍可能中招?
白名单、长度限制、类型转换这些手段单独用,都扛不住构造精巧的攻击。
ISNUMERIC(@input) = 1 → 不拦得住 ‘1e3’ 或 ‘1,2,3’,更拦不住后面接 OR 1=1 –LEN(@input) <= 10 → 攻击载荷可以很短,比如 ‘; DROP TABLE users–用 QUOTENAME() 处理对象名 → 安全,但仅限于数据库对象(表、列、schema),不能用于值;且必须配合白名单,否则 QUOTENAME(‘users; DROP TABLE log–‘) 会变成 [users; DROP TABLE log–],依然执行失败但可能泄露结构
MySQL / PostgreSQL 存储过程是否同样适用?
不适用。SQL Server 的 sp_executesql 是特例,它原生支持参数绑定;而 MySQL 的 PREPARE … EXECUTE 虽然语法类似,但参数只支持标量值,不支持表名/列名,且错误处理弱;PostgreSQL 的 EXECUTE … USING 更接近,但动态语句中仍需用 format() 拼接对象名——这时必须用 quote_ident() 或 quote_literal(),且不能混用。
跨数据库时,最稳的底线是:值一律参数化,对象名一律白名单 + 显式校验函数(如 PostgreSQL 的 pg_get_userbyid() 配合 schema 查询)。
参数化不是加个 @ 就完事,是整条执行链路里,用户输入从进来到执行完,一次都不许变成字符串的一部分。这点容易被忽略,但恰恰是防御成败的分水岭。

评论(0)