BEFORE INSERT 和 BEFORE UPDATE 触发器无法防御 SQL 注入,因其仅校验已到达表的合法数据,而注入发生在应用拼接 SQL 阶段;绕过方式包括 INSERT INTO ... SELECT、UPDATE 其他字段、纯 SELECT/LOAD_FILE() 等不触发表操作;SIGNAL 仅拦截入库脏数据,无法阻止注入执行;根本防护应是预处理语句、权限管控与错误屏蔽。

BEFORE INSERT 和 BEFORE UPDATE 触发器无法可靠拦截 SQL 注入攻击,它们不是防御层,而是数据校验层。
你看到的“用触发器过滤非法字符串”方案,在真实攻击链中基本失效。注入已经发生在应用拼接 SQL 的那一刻,触发器连执行机会都没有——它只对合法到达表的数据起作用,而注入者根本不需要走那条路。
INSERT INTO ... SELECT 绕过触发器是常态
触发器绑定在某张表上,比如 users,但攻击者完全不往 users 插数据:
- 构造
INSERT INTO logs SELECT password FROM users:目标表是logs,而触发器在users上,压根不触发 - 用
UPDATE改其他字段(如is_admin = 1),再靠业务逻辑间接提升权限,触发器只检查当前字段,不管业务语义 - 直接执行
SELECT ... UNION SELECT ...或LOAD_FILE(),这些语句根本不写入任何表,触发器完全沉默
只要攻击路径能绕开触发器所依附的表或事件,整套机制就形同虚设。
SIGNAL SQLSTATE 只能拦住已进来的脏数据,拦不住注入本身
触发器里的 SIGNAL SQLSTATE '45000' 确实能阻止非法值入库,比如价格 ≤ 0、邮箱不含 @ 符号等。但它生效的前提是:
- 应用层已把恶意字符串当作“合法参数”传给了 INSERT/UPDATE
- 数据已进入 MySQL 解析流程,并定位到具体表和字段
- 触发器被激活,且逻辑覆盖了该字段的全部风险模式
而 SQL 注入的典型入口(如 WHERE username = '$input')根本不会走到这一步——它在语法解析阶段就被执行了,甚至没生成“新行”供 NEW 引用。
真正该投入精力的地方:堵住源头,不是补漏
- 所有用户输入必须走
PDO::prepare()或mysqli_prepare(),禁止任何形式的字符串拼接 - 数据库账号禁用
FILE权限,secure_file_priv设为非空受限路径,local_infile = OFF - 启用严格 SQL 模式:
STRICT_TRANS_TABLES防止类型转换绕过,ERROR_FOR_DIVISION_BY_ZERO干扰盲注判断 - 报错信息绝不外泄,Web 层屏蔽 MySQL 原始错误,避免暴露表名、字段名
触发器适合做业务规则兜底(比如“订单金额不能为负”),不适合当安全网。把它当成防注入手段,等于在门锁坏掉时,拼命加固门后的窗帘。


















