CREATE TRIGGER无法防御二次SQL注入,因其仅在数据写入时执行,而二次注入发生在应用层将数据库读出的数据拼入新SQL语句时,此时触发器早已退出且不可见该拼接过程。

CREATE TRIGGER 不能防御二次 SQL 注入。
触发器在数据写入或更新之后才执行,而二次注入发生在应用层把从数据库读出的字段再次拼进新 SQL 语句时——此时触发器早已退出,完全不参与该过程。你查出一个含 ' OR 1=1 -- 的用户名,再用它拼 "SELECT * FROM logs WHERE user = '" + name + "'",这个拼接动作数据库根本看不见,更不会激活任何触发器。
为什么 BEFORE UPDATE 触发器也拦不住二次注入
常见误解是:在用户表上加个触发器,对 NEW.username 做 REPLACE 或正则过滤,就能“消毒”。但问题在于:
- 触发器只管入库那一刻,不管这数据将来被读出来怎么用
- 攻击者可以绕过触发器约束的字段,比如往
note字段存恶意 payload,而触发器没校验它 - 即使你过滤了单引号,攻击者存的是
admin%27%20OR%201%3D1(URL 编码),触发器默认不解码,直接放过 - SQL Server 的
TRIGGER无法访问原始查询语句,NEW和OLD只是解析后的值,不是输入源
真正该拦截二次注入的位置:应用层拼 SQL 前
二次注入本质是“信任错位”——你把数据库当成了可信边界,但它只是存储容器。防御必须卡在数据**重入执行前**那一步:
- 所有从 DB/缓存读出、且将用于动态拼接的变量,必须做类型+格式断言:
if (!/^[a-zA-Z0-9_]{1,32}$/.test(username)) throw Error() - 禁止对任何动态字段使用
sp_executesql拼接表名、列名或ORDER BY子句;必须用白名单校验:if (!['users', 'orders'].includes(tableName)) reject() - 避免
EXEC('SELECT * FROM ' + @table)这类写法;改用sys.sp_executesql配合参数占位符,仅对值参数化 - 对自由文本字段(如评论),若业务真需动态拼接,先转义再拼:
QUOTENAME(@input, '''')(注意:这只防单引号闭合,不解决逻辑绕过)
触发器在二次注入场景里唯一可能的辅助用途
极少数合规审计要求下,可让触发器记录可疑变更,但绝不用于拦截:
- 在敏感字段(如
is_admin、role)上建AFTER UPDATE触发器,把USER_NAME()、HOST_NAME()、变更前后值写入审计表 - 配合 SQL Server 的
fn_dblog()或扩展事件(Extended Events)捕获异常高频 UPDATE,作为事后分析线索 - 注意:
SIGNAL在 SQL Server 中不存在(那是 MySQL 的语法),想中断必须靠THROW或RAISERROR,但它们只能终止当前触发器上下文,不影响已发出的主语句
真正难的不是写触发器,而是确保每一处 SELECT 出来的值,在进入下一个 EXEC 或 sp_executesql 前,都经过上下文明确的校验——这个校验逻辑没法抽象成通用数据库规则,必须贴着业务代码写。

















