SQL注入长期位居Web安全威胁榜前列,不少团队试图把防御逻辑下沉到数据库内部。利用触发器在写入前拦截可疑字符串,是一种在数据库层设卡的想法。本文围绕具体实现与固有缺陷展开,说明这种过滤方式到底能解决什么、不能解决什么。

一、触发器拦截字符串的基本原理
触发器是数据库在表发生插入、更新或删除操作时自动执行的存储程序。借助before insert或before update类型的触发器,我们可以在数据真正落盘前检查新行字段内容。若发现字段中含有典型的注入特征,例如注释符、尖括号或union关键字,就可以主动抛出错误,使整条语句回滚。
这种机制的优势在于与业务代码解耦,无论前端用何种语言、是否遗漏过滤,只要数据进入指定表就会经过统一校验。但它只能看到单条记录里的字段值,无法感知整条SQL的拼接上下文,也就难以区分正常业务输入与恶意载荷。
二、MySQL中编写过滤触发器的示例
下面以MySQL 8.0为例,创建一张用户表,并为其编写before insert触发器,拒绝包含常见注入片段的内容写入用户名与备注字段。
-- 创建测试表
CREATE TABLE app_user (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50),
remark VARCHAR(200)
);
-- 创建触发器,过滤非法字符串
DELIMITER //
CREATE TRIGGER trg_app_user_insert
BEFORE INSERT ON app_user
FOR EACH ROW
BEGIN
IF NEW.username REGEXP '(--|;|<|>|union|select|drop)' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '非法字符串被触发器拦截';
END IF;
IF NEW.remark REGEXP '(<|>|script|/\*)' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '备注含非法标签被拦截';
END IF;
END//
DELIMITER ;
-- 正常插入
INSERT INTO app_user(username, remark) VALUES ('tom', '普通用户');
-- 恶意插入,将被触发器阻止
INSERT INTO app_user(username, remark) VALUES ('admin--', '测试');
上述代码中,REGEXP用于匹配正则模式,SIGNAL语句模拟异常使插入失败。字段名前的NEW代表即将插入的行对象。该写法对明确特征串有效,例如攻击者直接在用户名后追加双减号注释。
不过正则匹配是静态规则,面对编码变形就无能为力。比如把<script>写成十六进制或借助数据库字符集转换,触发器里的字面量匹配会直接放过。因此示例仅展示了思路,生产环境还需配合更完整的黑名单或白名单策略。
三、触发器层过滤的真实局限
第一,触发器无法获取原始SQL语句,只能检查字段值本身。许多注入利用的是上下文拼接,例如数值型参数未加引号,此时字段值看似正常,但拼出的SQL已改变语义。触发器对此类场景毫无感知。
第二,性能开销不可忽视。每张表每次写操作都要执行正则匹配甚至多条判断,高并发写入场景下可能成为瓶颈。而且不同数据库对触发器内调用函数的限制不同,部分引擎不支持复杂字符串处理。
第三,它容易给人虚假的安全感。开发团队若认为数据库已兜底,便放松Web层参数化查询的改造,反而扩大风险面。正确做法仍是以预编译语句为主,触发器仅用作辅助审计或最后防线。
四、更合理的防护组合方案
在应用代码中统一使用预编译语句或ORM框架,是从根源消灭注入的最佳实践。数据库账户遵循最小权限原则,禁用不必要的FILE、DROP权限,能降低万一被注入后的破坏范围。
若确实需要在库内加一道闸门,可把触发器设计成只记录疑似非法写入的审计日志,而非直接阻断,避免误伤正常业务。同时结合Web应用防火墙与输入白名单,形成多层防御。只有理解各层定位,才能让系统既安全又稳定。