必须同步回收FILE权限并设置secure_file_priv=NULL,缺一不可;仅REVOKE FILE不生效,因攻击者可通过LOAD DATA INFILE、UDF插件或未重启的配置绕过限制。

必须同时回收FILE权限并设置secure_file_priv为NULL,缺一不可——只做其中一项,攻击者仍可能绕过限制。
为什么单靠REVOKE FILE不够
FILE权限本身不操作数据库表,但它能配合LOAD DATA INFILE、SELECT ... INTO OUTFILE、LOAD_FILE()读写服务器任意文件。即使你执行了REVOKE FILE ON *.* FROM 'app_user'@'%',以下情况仍可能触发风险:
- 旧版本MySQL(如5.6)在
secure_file_priv为空字符串''时,存在绕过FILE权限检查的路径 - 用户若已加载UDF插件(如
sys_exec),可通过SELECT sys_exec('cat /etc/passwd')间接读取文件 - 某些备份工具或中间件会以root身份连接并隐式启用FILE,权限回收未覆盖该账号
回收FILE权限的正确操作顺序
顺序错误会导致权限残留或误判生效状态:
- 先确认目标用户:用
SHOW GRANTS FOR 'app_user'@'%'查是否真有FILE权限;避免对不存在的用户执行REVOKE(命令静默成功但无实际效果) - 执行回收:
REVOKE FILE ON *.* FROM 'app_user'@'%'—— 注意必须是ON *.*,写成ON mydb.*语法错误 - 检查残留:
SELECT User,Host,File_priv FROM mysql.user WHERE File_priv='Y',重点看root和运维账号是否也被误留 - 验证失效:
SELECT LOAD_FILE('/etc/passwd')应返回NULL且无错误;SELECT 1 INTO DUMPFILE '/tmp/test'应报错ERROR 1045 (28000)
secure_file_priv必须设为NULL,不能只设目录
很多团队把secure_file_priv设成/var/lib/mysql-files/就以为安全了,这是误解:
- 该变量只是路径白名单,不是开关——设为具体目录后,用户仍可执行
INTO OUTFILE,只是被限定在该目录下 - 只要FILE权限还在,攻击者就能往
/var/lib/mysql-files/写Webshell,再通过其他漏洞(如SSRF)触发解析 - 生产环境唯一安全值是
NULL:SELECT @@secure_file_priv必须返回NULL;空字符串''极度危险,等同于完全放开 - 修改方式:只能在
my.cnf中写secure_file_priv = NULL,然后重启mysqld;SET GLOBAL动态设置无效
容易被忽略的隐蔽残留点
回收后仍需手动检查三处,否则FILE能力可能暗中存活:
- UDF函数:运行
SELECT * FROM mysql.func,若有非系统函数(如lib_mysqludf_sys),必须DROP FUNCTION sys_eval逐个清理 - 插件路径:查
SELECT @@plugin_dir,确保不指向/tmp或/var/tmp等可写目录,防止恶意CREATE FUNCTION ... SONAME - 角色继承:如果用户属于某个角色(如
'backup_role'),而该角色被授予了FILE,需额外执行REVOKE 'backup_role' FROM 'app_user'@'%'
真正安全的FILE权限回收,不是“执行一条REVOKE”,而是“切断所有可能的入口路径+验证每条路径确实堵死”。尤其要注意旧连接不刷新权限、UDF残留、以及secure_file_priv配置未重启生效这三点,它们在真实攻防对抗中最常被利用。


















