仅关闭local_infile不够,因load data infile(无local)仍可执行,须同时回收file权限并设secure_file_priv = null(8.0.17+)或''(旧版),且客户端连接需显式禁用local_infile。

为什么只关local_infile不够
很多人以为在my.cnf里写local_infile = OFF就万事大吉,结果发现LOAD DATA INFILE(不带LOCAL)还能执行。这是因为local_infile只控制客户端本地文件上传行为,对服务端读取服务器文件的LOAD DATA INFILE完全无效。后者依赖的是FILE权限和secure_file_priv路径限制,两者必须同时处理。
必须回收FILE权限并验证生效
FILE是全局权限,不能按库或表授予/撤销,漏掉这步等于留后门。操作时注意:
- 执行
REVOKE FILE ON *.* FROM 'username'@'host';,不是FROM 'username'@'%'——主机名必须精确匹配 - 立即执行
FLUSH PRIVILEGES;,否则权限缓存可能延迟生效 - 检查残留:运行
SELECT User,Host,File_priv FROM mysql.user WHERE File_priv='Y';,确保目标用户不在结果中 - 特别留意
root账号:很多环境默认开启FILE,但它对日常管理并非必需
secure_file_priv设成NULL比设目录更彻底
设secure_file_priv = /var/lib/mysql-files只是把读写锁进一个目录,只要用户还有FILE权限,仍能从该目录读取my.cnf、导出表结构甚至写入恶意文件。真正一劳永逸的做法是:
- MySQL 8.0.17+:在
[mysqld]段写secure_file_priv = NULL,重启后所有LOAD DATA INFILE和SELECT INTO OUTFILE直接报错ERROR 1290 - 旧版本:设为空字符串
secure_file_priv = ''效果相同,但这是高危配置——如果没同步回收FILE权限,MySQL启动会失败 - 绝对不要设为
/或留空,那等于开放整个磁盘
客户端连接也要显式禁用local_infile
服务端关了,客户端驱动仍可能默认开启local_infile,尤其老项目或ORM封装层。常见场景:
- PHP
mysqli:必须在mysqli_real_connect()前调用mysqli_options($conn, MYSQLI_OPT_LOCAL_INFILE, false) - Python
pymysql:初始化连接时传local_infile=False,漏掉就白配 - Java
Connector/J:URL里别带&allowLoadLocalInfile=true,8.0+默认false但可被覆盖 - Docker/K8s环境:确认挂载的
my.cnf确实被加载,SELECT @@local_infile返回0才算真生效
最易忽略的点:secure_file_priv = NULL和REVOKE FILE必须成对出现。只做前者,攻击者可能通过其他账户或未审计的角色拿到FILE;只做后者,secure_file_priv设得再严,也挡不住已授权用户的路径穿越尝试。











