应用层堵sql注入须用预处理语句(如mysqli_prepare)或orm参数化,禁用字符串拼接;存储过程/触发器中动态sql需白名单校验;mysql审计日志和内置功能无法实时拦截,dbfw须部署在应用与数据库间且流量必经。

MySQL应用层怎么堵住SQL注入的入口
SQL注入不是数据库的问题,是应用拼接SQL时没做参数化导致的。MySQL自身不提供“自动检测并拦截”功能,所有所谓“防注入”都得在应用代码里做,或者加中间件。
常见错误现象:mysql_query("SELECT * FROM user WHERE id = " . $_GET['id']) 这种写法,id=1 OR 1=1 直接绕过权限查全表。
- 必须用预处理语句:
mysqli_prepare()或PDO::prepare(),变量只进bind_param(),绝不拼进SQL字符串 - ORM如Laravel Eloquent、Django ORM默认参数化,但手写
raw()或extra()时会退化回拼接,要重点盯 - 数字类型字段别图省事用
intval()兜底——它不防1e2这种科学计数,而filter_var($x, FILTER_VALIDATE_INT)更稳
为什么不能靠MySQL内置审计日志做实时WAF
MySQL 5.7+ 的audit_log插件或8.0的component_audit只能记录已执行的SQL,属于事后审计,不是拦截机制。它连“这条SQL是否危险”都不判断,更不会中断请求。
使用场景:合规检查、事故复盘;不是防护手段。
- 开启后日志体积暴涨,
audit_log_policy=ALL可能让磁盘IO打满,尤其高并发写入时 - 日志内容含原始SQL,若含敏感字段(如
password),直接违反GDPR/等保要求 - 无法区分正常模糊搜索(
WHERE name LIKE '%张%')和恶意注入(WHERE name LIKE '%a%' OR '1'='1'),规则匹配极不可靠
数据库防火墙(DBFW)和Web应用防火墙(WAF)的根本区别
真正在生产环境起作用的“数据库WAF”,其实是部署在应用和MySQL之间的代理层,比如ProxySQL + 自定义规则,或商业产品如Imperva、Fortinet FortiDB。它们不是MySQL插件,也不运行在数据库服务器上。
关键点:所有流量必须流经它,否则无效。如果应用直连MySQL,再好的DBFW也形同虚设。
- ProxySQL的
mysql_query_rules表可匹配match_pattern,例如拦截含UNION SELECT或information_schema的请求,但正则写错会导致误杀(如正常字段名含union_id) - 商业DBFW通常支持学习模式,先观察流量生成白名单,但上线前必须人工核对——自动学习会把测试用的
1=1也当合法流量放行 - 注意连接池行为:DBFW若不透传客户端IP,
SHOW PROCESSLIST看到的全是防火墙机器的IP,排查来源困难
最常被忽略的盲点:存储过程和触发器里的动态SQL
很多人以为“用了预处理就万事大吉”,结果在MySQL存储过程中写了SET @sql = CONCAT('SELECT * FROM ', table_name); PREPARE stmt FROM @sql;——这仍然是注入高危区,且WAF/DBFW完全看不见,因为SQL在服务端内部拼接执行。
- MySQL没有安全的动态表名拼接方案,
table_name必须来自白名单校验(如IF table_name NOT IN ('user','order') THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid table'; END IF;) - 触发器中禁止调用
sys_eval()这类UDF,很多DBFW规则库根本不管触发器上下文 - 应用层WAF(如ModSecurity)对存储过程调用
CALL proc_name('abc')无感知,它只解析HTTP层参数,不解析MySQL协议包里的CALL payload
事情说清了就结束。











