直接查 mysql.user 表的 file_priv 字段最准,select user, host, authentication_string, plugin from mysql.user where file_priv = 'y' 可精准定位所有显式拥有该权限的高危账号。

直接查 mysql.user 表的 File_priv 字段最准
FILE 权限是 MySQL 里最危险的权限之一,它允许用户通过 SELECT ... INTO OUTFILE 或 LOAD DATA INFILE 读写服务器任意文件(包括 /etc/passwd、/root/.bash_history),攻击者拿到这个权限基本等于拿下了操作系统。别信“只给了 SELECT 就安全”——只要 File_priv = 'Y',就具备文件系统突破能力。
执行这条 SQL 即可定位所有显式拥有该权限的账号:
SELECT User, Host, authentication_string, plugin FROM mysql.user WHERE File_priv = 'Y';
注意三点:
-
File_priv是字符型字段,必须用= 'Y',不能写成= 1或IS TRUE - 结果中
Host = '%'的账号风险最高,意味着从任意 IP 都能触发文件操作 - 务必一并查出
authentication_string和plugin:如果值为空或为'*',说明这账号本身也没密码,属于双重高危
别漏掉通过 mysql.db 表间接获得的 FILE 权限
mysql.user 只存全局权限,而 FILE 权限只能在全局级别授予(MySQL 不支持库级或表级 FILE),所以理论上不会出现在 mysql.db 等表里。但实际运维中,有人会误操作:GRANT FILE ON test.* TO 'u'@'%' 语句虽被 MySQL 拒绝并报错,但某些旧版本或定制分支可能未严格校验,导致元数据残留。稳妥起见,加查一句:
SELECT User, Host, Db, Select_priv, Insert_priv, Update_priv, Delete_priv FROM mysql.db WHERE User != '' AND (Select_priv = 'Y' OR Insert_priv = 'Y' OR Update_priv = 'Y' OR Delete_priv = 'Y') AND Db = 'mysql';
重点看 Db = 'mysql' 且权限全开的记录——这类账号一旦配合 INSERT/UPDATE 往 mysql.user 表写数据,就能伪造出带 FILE 权限的新用户。
SHOW GRANTS 不能替代表扫描,尤其对角色用户
如果你只对某个用户跑 SHOW GRANTS FOR 'app'@'%',很可能漏掉真实风险。原因有两个:
- MySQL 8.0+ 支持角色(ROLE),
SHOW GRANTS默认不展开角色继承的权限。一个用户可能没直授 FILE,但被赋予了含 FILE 的角色 - 如果用户权限来自
CREATE USER+GRANT组合,而你只查了当前生效权限,却没注意到之前用INSERT INTO mysql.user手动改过File_priv字段(这种操作在备份恢复后很常见)
要确认完整权限链,得查:
SELECT FROM_USER, TO_USER, TO_HOST FROM mysql.role_edges WHERE TO_USER = 'app';
再对每个查到的角色执行 SHOW GRANTS FOR 'role_name'@'%',看是否含 FILE。
查到之后,优先 REVOKE 而不是删用户
发现 File_priv = 'Y' 的账号,第一反应不该是 DROP USER,而是先回收权限:
REVOKE FILE ON *.* FROM 'backup_user'@'%';
理由很实际:
- 很多备份脚本依赖
mysqldump --tab,背后就是 FILE 权限,直接删用户会导致备份失败 - 某些监控 agent(如 Prometheus mysqld_exporter)用
SELECT ... INTO OUTFILE导出指标,删了它整个大盘就断 -
REVOKE立即生效,不用FLUSH PRIVILEGES;而DROP USER后若没及时清理应用配置,服务会持续报错重连
真正难的不是找到谁有 FILE 权限,而是弄清这个权限到底被哪个定时任务、哪条 shell 脚本、哪个容器健康检查在静默调用——它往往藏在没人维护的 crontab 或离线脚本里,而不是主业务代码中。











