必须联动账号权限、sql执行路径与文件系统限制才有效:禁用file/process/super权限需revoke后用show grants验证;secure_file_priv设为受限目录并关闭local_infile;清理高危udf并拒绝对应execute权限;waf无法覆盖已认证会话,须结合数据库防火墙拦截“最后一公里”注入。

数据库防火墙不是“开个开关就完事”的组件,它必须和账号权限、SQL执行路径、文件系统限制三者联动才真正有效。单独配置某一项,大概率被绕过。
MySQL 中禁用 FILE/PROCESS/SUPER 权限的实操要点
这三个权限是 SQL 注入后提权的黄金跳板,但 REVOKE 命令本身不报错不代表权限已被移除——它只是“尝试移除”。
- 必须执行
REVOKE FILE ON *.* FROM 'app_user'@'%';、REVOKE PROCESS, SUPER ON *.* FROM 'app_user'@'%';两步显式回收 - 紧接着用
SHOW GRANTS FOR 'app_user'@'%';确认输出里已无FILE、PROCESS、SUPER字样,否则说明账号可能继承自角色或全局权限未清理干净 - 生产环境要定期巡检:尤其在 DBA 手动
GRANT后未同步回收、或使用了CREATE USER ... IDENTIFIED WITH mysql_native_password等隐式赋权场景下,容易漏掉
secure_file_priv 和 local_infile 的协同关闭
secure_file_priv 设为空或 NULL 会直接禁用 LOAD DATA INFILE,但旧版 PHP 应用(如某些 CMS 的备份模块)可能因此报错;设为具体目录更可控,也便于审计日志。
- 启动时加参数:
--secure-file-priv=/var/lib/mysql-files/(推荐,避免运行时动态设置需SUPER权限) - 同时确保
local_infile关闭:SET GLOBAL local_infile = OFF;,并检查 MySQL 启动参数含--local-infile=0 - 验证是否生效:
SELECT @@secure_file_priv;返回路径,SELECT @@local_infile;返回OFF
清理高危 UDF 和函数执行权限
攻击者常通过 sys_exec()、sys_eval() 等 UDF 实现远程命令执行,这类函数不是 MySQL 自带,而是通过 lib_mysqludf_sys 动态加载的,必须主动识别并清除。
- 检查是否加载:
SELECT * FROM mysql.func WHERE name LIKE '%sys%';,若有结果立即DROP FUNCTION sys_exec;(同理处理sys_eval、sys_getpid) - 对业务账号显式拒绝执行权限:
REVOKE EXECUTE ON FUNCTION mysql.sys_exec FROM 'app_user'@'%'; - 注意:UDF 删除后需重启 mysqld 才彻底卸载,否则仍可能被调用
WAF 与数据库防火墙的职责边界
Web 层 WAF(如 ModSecurity)能拦截大部分 UNION SELECT、sleep() 类 payload,但它看不到连接池复用后的内部查询、也拦不住已认证用户发来的恶意语句。数据库防火墙补的是这一段“最后一公里”。
- ModSecurity 必须开启
SecRequestBodyAccess On,否则 POST body 里的注入载荷进不到规则匹配范围 - 金仓(KingbaseES)等国产数据库内置 SQL 防火墙支持学习模式生成白名单,但切换到“报错模式”前务必完成至少 72 小时全量业务流量采集,否则误杀率极高
- GreenSQL 这类代理型防火墙监听端口(如
3307),应用必须改连该端口——这点常被忽略,导致配置做完却没流量经过防火墙
最易被忽略的点:所有配置变更后,必须用真实业务请求触发一次 SELECT ... INTO OUTFILE、LOAD_FILE()、UNION SELECT 等操作,验证是否真被阻断。凭 “命令执行成功” 或 “配置文件保存了” 就认为防护生效,是线上事故最常见的起因。










