必须同步关闭local_infile=0、secure_file_priv=null、skip_symbolic_links=yes并回收file权限,三者缺一不可;仅revoke file不生效,因攻击者仍可通过load data local infile等路径绕过。

只回收 FILE 权限远远不够,必须同步关闭 local_infile、设死 secure_file_priv、禁用符号链接,否则攻击者仍能读取 /etc/passwd 或 ~/.my.cnf。
为什么 REVOKE FILE 不等于安全落地
FILE 权限本身不控制语句执行,而是配合具体 SQL 功能起作用。只要用户还能执行 LOAD DATA LOCAL INFILE,客户端(如 pymysql、mysql CLI)就会主动读本地文件并上传——服务端是否开启 local_infile 并不影响这一行为。常见错误包括:
- 仅执行
REVOKE FILE ON *.* FROM 'user'@'host';,但未检查SELECT @@local_infile;是否为OFF - 误以为
SHOW VARIABLES LIKE 'have_symlink'返回NO就代表符号链接已禁,其实它只是只读状态标识 - 没查
mysql.user表确认File_priv='Y'的账号是否清零,遗漏了匿名用户或 test 库默认账号
必须同时关闭的三个关键开关
缺一不可,任一遗漏都可能被绕过:
-
local_infile=0:加在[mysqld]段,重启生效;客户端也要显式禁用,例如mysql --local-infile=0 -u user -p -
secure_file_priv = NULL:写在[mysqld]段,重启生效;设为空字符串''是最危险的配置 -
skip_symbolic_links = yes:唯一有效的符号链接禁用项,必须放在[mysqld]段;symbolic-links=0在 8.0.33+ 已被忽略
验证命令:SELECT @@local_infile, @@secure_file_priv, @@skip_symbolic_links; 三者应分别返回 OFF、NULL、1。
FILE 权限回收后仍需检查的隐蔽路径
即使权限已撤,旧配置或残留能力仍可能暴露:
- 检查是否加载了 UDF 插件:
SELECT * FROM mysql.func;,非必要函数应手动DROP FUNCTION - 确认
plugin_dir是否指向无效路径(如/nonexistent/path),防止恶意CREATE FUNCTION ... SONAME - 验证
LOAD_FILE()是否真的失效:SELECT LOAD_FILE('/etc/passwd');应返回NULL且无错误 - 测试
SELECT ... INTO DUMPFILE和INTO OUTFILE是否均被拒绝,而非仅报错权限不足
最容易被忽略的配置加载问题
MySQL 会按顺序读多个配置文件,只改 /etc/my.cnf 而忽略 /etc/mysql/my.cnf 或 ~/.my.cnf,等于白做:
- 用
mysqld --verbose --help | grep "Default options"查实际加载路径 - 所有生效的配置文件都要统一设置
local_infile=0、secure_file_priv=NULL、skip_symbolic_links=yes - 改完必须
systemctl restart mysqld,仅FLUSH PRIVILEGES不生效 - 某些 Docker 镜像或云数据库托管服务不支持修改这些变量,需确认平台限制
真正有效的验证不是看变量值,而是行为:尝试 LOAD DATA LOCAL INFILE '/etc/passwd' 和 SELECT ... INTO OUTFILE '/tmp/test',两个都必须失败。











