sql server防sql注入核心是:用户输入进动态sql必须用sp_executesql参数化,禁用exec;表名等对象名须quotename()加白名单校验;输入需强类型验证与长度限制;执行账号须最小权限。

SQL Server项目中防SQL注入,核心就一条:所有用户输入进动态SQL的地方,必须用 sp_executesql 做参数化,绝不能用 EXEC(@sql) 拼字符串。
为什么 sp_executesql 是底线,而 EXEC 必须禁用
因为 EXEC(@sql) 把整个字符串交给SQL引擎执行,只要字符串里混入恶意代码(比如 ' OR 1=1 --),就会原样执行;sp_executesql 则把SQL结构和数据彻底分离——参数值不参与语法解析,只作为纯数据绑定。
- 错误写法:
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Users WHERE name = ''' + @inputName + ''''; EXEC(@sql);—— 即使加了单引号转义,也挡不住编码绕过、嵌套注释等手法 - 正确写法:
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Users WHERE name = @name'; EXEC sp_executesql @sql, N'@name NVARCHAR(50)', @name = @inputName; - 注意:第二个参数是N开头的字符串,必须声明每个参数名+类型,顺序、类型、长度都要匹配,否则可能隐式转换失败或截断
表名、列名这类动态对象名怎么处理
sp_executesql 无法参数化表名、排序字段、GROUP BY 字段——这是T-SQL语法限制,不是你写得不够好。此时唯一安全做法是 QUOTENAME(),不是自己 REPLACE(@name, '''', '''''')。
- 危险示例:
SET @sql = 'SELECT * FROM ' + REPLACE(@tableName, '''', '''''') + ' WHERE id = @id';——REPLACE对[user; DROP TABLE test]这类名字完全无效 - 安全写法:
SET @sql = 'SELECT * FROM ' + QUOTENAME(@tableName) + ' WHERE id = @id';——QUOTENAME()输出带方括号的标识符,SQL Server 会按对象名解析,不会执行 - 但仅靠
QUOTENAME()不够:必须配合白名单校验,比如查sys.tables确认该表真实存在且在允许访问范围内
输入验证和权限控制不是可选项,而是兜底层
参数化能挡住99%的注入,但业务逻辑漏洞和权限失控会让它前功尽弃。
- 对数字型输入,别信前端传来的“看起来像数字”的字符串:用
TRY_CAST(@input AS INT)或ISNUMERIC()(注意后者有缺陷)做强类型判断,失败就直接拒绝 - 对字符串字段,用白名单正则匹配:
@input NOT LIKE '%[^a-zA-Z0-9_ ]%',比黑名单过滤(如删--、;)可靠得多 - 存储过程执行账号必须最小权限:不能给
db_owner,不能跨库查询,不能调用xp_cmdshell;哪怕注入成功,也只能查一张表 - 长度检查必须前置:
IF LEN(@inputName) > 50 BEGIN RAISERROR('用户名超长', 16, 1) RETURN END,防止缓冲区截断攻击
真正容易被忽略的点是:动态对象名处理和权限控制这两层,常被当成“次要优化”跳过。但一旦表名拼接出问题,或者账号权限过大,前面所有参数化努力都会失效。











