必须用代理层,因其能拦截所有数据库流量且不依赖业务代码改造;proxysql是唯一可落地的生产级选择,需正确配置match_digest与match_pattern规则、字符集对齐及权限收敛等关键点。

为什么必须用代理层,而不是改应用代码
应用层参数化查询(如 PDO::prepare)能防住 95% 的注入,但只要存在一处 mysqli_query("SELECT * FROM $table WHERE id = " . $_GET['id']) 这类拼接,或报表引擎、ETL 工具直连数据库,整个防护就形同虚设。代理层是唯一能拦截所有流量、不依赖业务代码改造的兜底方案。
ProxySQL 是当前唯一可落地的生产级选择
MySQL Proxy 已停止维护,Lua 脚本难调试、高并发下抖动明显;ProxySQL 支持热加载规则、SQL 指纹匹配、响应统计,且完全兼容 MySQL 协议,零客户端修改即可接入。
关键配置点:
-
mysql_query_rules表中写规则,用match_pattern匹配原始 SQL 流,例如.*[;\x00]\s*(drop|shutdown|kill)\b.* - 必须设
case_sensitive = 0,否则UnIoN或seLect就会漏过 - 对变形绕过要容忍空白符:
U[s /*]+N[s /*]+I[s /*]+O[s /*]+N比单纯UNION更可靠 - 别开
mysql-eventslog全量日志——吞吐下降 30%+,只对apply = 1的拦截规则启用 error 级日志
match_digest 和 match_pattern 必须配合使用
match_digest 对 SQL 做标准化(去空格、转小写、参数占位),适合匹配语义指纹,比如 SELECT * FROM users WHERE id = 1 和 select/*abc*/ * from users where id= ? 指纹一致;但它会把函数参数抹掉,LOAD_FILE('/etc/passwd') 变成 LOAD_FILE(?),无法识别路径恶意性。
所以实际规则要分层:
- 用
match_digest拦永真条件、基础 union/select 模式 - 用
match_pattern扫描高危函数名:.*load_file\(|.*into\s+dum[filep]|.*information_schema\..* - GBK 宽字节场景下,必须在
mysql_servers表里显式设charset = gbk,否则截断点识别错误
权限收敛和 secure_file_priv 是代理层失效时的最后防线
代理层再强,也挡不住攻击者拿到账号密码后直接 telnet 连库。所以必须同步做数据库侧加固:
- 执行
REVOKE FILE, PROCESS, SUPER ON *.* FROM 'app_user'@'%',再用SHOW GRANTS FOR 'app_user'@'%'验证是否真正移除 -
secure_file_priv必须设为非空受限目录(如/var/lib/mysql-files/),不能为NULL或空字符串——否则LOAD DATA INFILE被禁用,但旧应用可能崩 - 禁用
local_infile:启动参数加--local-infile=0,或运行时执行SET GLOBAL local_infile = OFF - 检查是否加载了
sys_exec等 UDF:SELECT * FROM mysql.func WHERE name LIKE '%sys%',有则立即DROP FUNCTION sys_exec
真正容易被忽略的是:代理层规则写得再密,若后端 MySQL 用的是 GBK 字符集而 ProxySQL 按 UTF-8 解析,注释截断、宽字节绕过就会直接失效——这个字符集对齐点,90% 的部署文档都没提。











