sp_executesql仅在三要素齐全时才防二次注入:SQL模板不含值、参数类型显式声明且带长度、参数值带等号传入;动态对象名须白名单校验,读库数据必须重检。

SQL存储过程本身不是“免疫”二次注入的,关键在于怎么写。哪怕用了存储过程,只要里面出现 EXEC(@sql) 拼接字符串、或把数据库读出的数据直接拼进新SQL,风险就还在。
为什么 sp_executesql 用了还是被绕过?
很多人以为“用了 sp_executesql 就安全了”,其实它只在三要素齐全时才真正起作用:SQL模板不含值、参数类型显式声明、参数值带等号传入。漏掉任一环,等于白用。
-
@sql字符串里出现' + @name + '这类拼接——立刻退化成EXEC级别风险 -
@params写成@status INT却没写长度(如@name NVARCHAR(50)),攻击者可能用超长字符串触发隐式转换绕过校验 - 第三个参数写成
@status = @input,而@input是从表里查出来的——这等于把不可信数据又塞回了参数化流程的“入口”
动态对象名(表名/列名)不能参数化,怎么办?
SQL Server 不允许把表名、列名、排序字段当参数传,硬拼是唯一路径,但必须加锁——QUOTENAME() 只防单引号闭合,不防 ]; DROP TABLE x; -- 这种结尾注入。
- 查系统视图确认存在且归属预期 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) - 排序字段、分组字段等走硬编码白名单:
IF @sort_col NOT IN ('id', 'created_at', 'status') THROW 50000, 'Invalid sort column', 1 - 绝对不要用
OBJECT_ID(@table_name)做存在性判断——它不校验 schema,也不防恶意构造的 object_id 值
从数据库读出的数据,再用时是否可信?
不。哪怕是从 sys.tables 或 sys.columns 读的,也不能默认可信。因为这些视图内容可被高权限用户篡改,或因误操作写入非法值。所有读库再用的数据,都得重检。
- 读出的用户名、配置项、日志字段,如果后续要拼进新SQL,必须重新走白名单或长度+字符集校验
- 若必须用于动态SQL,优先转为参数化占位符;实在不行,先
REPLACE掉分号、括号、注释符,再QUOTENAME,最后加白名单兜底 - 特别注意时间字段、状态码这类看似“安全”的数值型字段——它们也可能被构造为
1; WAITFOR DELAY '0:0:5'等延迟注入 payload
二次注入最麻烦的地方不在技术实现,而在思维惯性:人容易对“已经入库的数据”放松警惕。只要数据流中存在“读库 → 拼SQL”这个环节,就必须按不可信输入处理,不能靠“它之前存过”来免责。

















