sql server二次注入发生在数据从数据库读出后未经校验又拼入新sql时;典型场景为恶意输入入库后再被读取拼接执行,转义过滤无法防御因存储值在后续查询中仍被解释为sql语法,必须对所有读出数据参与拼接前强制参数化。

SQL Server 二次注入到底发生在哪个环节
二次注入不是在第一次拼接 SQL 时触发,而是在数据从数据库读出后、未经校验又拼进新 SQL 时爆发。典型场景是:用户提交 admin' OR 1=1-- → 后端用 sp_executesql 或 EXEC() 存入表字段 → 管理员后台读取该字段值(比如 user_remark)→ 直接拼进 SELECT * FROM logs WHERE remark = 'xxx' → 执行时被利用。
入库前转义过滤为什么根本防不住二次注入
对输入做 REPLACE(@input, '''', '''''') 或用正则删掉单引号,只解决一次拼接风险,但无法阻止“存储的恶意字符串在后续查询中再次被解释为 SQL 语法”。更麻烦的是:'''' 这种转义只对 EXEC() 有效,对 sp_executesql 参数化方式完全无效——后者压根不走字符串解析流程。
- 转义后的数据在 SELECT 出来时仍是原始恶意内容(比如存进去的是
admin'' OR 1=1--,读出来还是它) - 如果下游代码用
CONCAT('WHERE name = ''', @name, '''')拼接,照样执行成功 - JSON 字段、日志备注、动态报表条件等“非主键/非索引”字段最容易成为二次注入温床
真正有效的防御必须分层落地
单靠入库过滤是幻觉。必须切断“读出即执行”的链路:
- 所有从数据库读出的值,参与 SQL 拼接前,必须重新走参数化 —— 即使是
sp_executesql的@params也要显式声明类型,不能依赖隐式转换 - 禁止使用
EXEC(@sql),哪怕 @sql 是拼出来的;改用sp_executesql @sql, N'@p1 NVARCHAR(50)', @p1 = @value - 对动态列名/表名等无法参数化的部分,严格白名单校验(如
IF @col NOT IN ('name','email','status') THROW 50000, 'Invalid column', 1) - 业务上能用视图或存储过程封装的逻辑,绝不把原始字段值暴露给应用层拼 SQL
哪些地方最容易漏掉二次注入检查
开发最常忽略的是“非显式 SQL 构造”场景:
- ORM 的
Where()方法传入字符串(如 Entity Framework 的.Where("Status = '" + dbValue + "'")) - 日志审计模块读取用户可控字段后,再拼进
INSERT INTO audit_log的 VALUES 部分 - SSIS 包里用变量拼接 SQL Command,变量来源是上游查询结果
- SQL Server Agent 作业脚本中引用
$(ESCAPE_NONE(SQLCMD))类变量,而该变量值来自表查出
这些位置不会报错,也不会触发 WAF,但只要数据流跨了两次查询边界,就存在风险。比一次注入更难审计,因为静态扫描工具通常只盯住第一次输入点。










