必须服务端和客户端双侧禁用local_infile、回收FILE权限、设secure_file_priv=NULL、禁用符号链接,缺一不可;仅服务端设OFF无法阻止客户端主动读取/etc/passwd等敏感文件。

仅在服务端设 local_infile = OFF 不足以防范任意文件读取——攻击者仍可通过客户端(如 PyMySQL、mysql CLI)主动读取 /etc/passwd 或 ~/.my.cnf 并外泄,必须服务端 + 客户端双侧禁用,且同步回收 FILE 权限、关闭符号链接、锁定 secure_file_priv。
检查当前 local_infile 是否真被禁用
只看配置文件或执行 SET GLOBAL local_infile = OFF 不代表生效。必须连上 MySQL 后验证:
-
SELECT @@global.local_infile;返回0(不是字符串'OFF'),才算服务端落地 -
SHOW VARIABLES LIKE 'local_infile';若返回空,说明变量根本没加载——常见原因是写到了[mysql]段而非[mysqld]段 - 用默认方式连接:
mysql -u user -p(不带--local-infile),再执行LOAD DATA LOCAL INFILE '/tmp/x' INTO TABLE t;,应报错ERROR 1148 (42000)或ERROR 2068 (HY000)
服务端配置必须写对位置并重启
local_infile = 0 必须放在 [mysqld] 段,且需重启 mysqld 进程才生效。临时 SET 命令在重启后清零,不可依赖:
- Linux 常见路径:
/etc/my.cnf、/etc/mysql/mysql.conf.d/mysqld.cnf;Docker 需确认挂载是否覆盖 - 不能写成
local-infile = 0(带短横线)——MySQL 8.0+ 只认下划线形式local_infile - 避免被其他段覆盖:检查
[server]、[mysqld_safe]是否存在同名配置且值为ON - 重启后务必执行
SELECT @@local_infile;确认结果为0
客户端连接必须显式禁用 local_infile
服务端拒绝请求 ≠ 攻击链路消失。只要客户端还支持协商 LOCAL 能力,SQL 注入就能触发它读本地文件:
- MySQL CLI:启动加
--local-infile=0,或在~/.my.cnf的[client]段写local_infile = 0 - PyMySQL:
pymysql.connect(..., local_infile=False)—— 注意默认值不等于安全,某些打包版本默认开启 - mysql-connector-python:
mysql.connector.connect(..., allow_local_infile=False) - PHP mysqli:
mysqli_options($conn, MYSQLI_OPT_LOCAL_INFILE, false),且必须在mysqli_real_connect()前调用 - Node.js mysql2:
new Pool({ localInfile: false })
禁用 local_infile 只是起点,FILE 权限和符号链接必须同步清理
local_infile 控制的是客户端上传,FILE 权限控制的是服务端读取服务器本地文件(如 LOAD DATA INFILE、SELECT ... INTO OUTFILE)。两者独立,缺一不可:
- 执行
REVOKE FILE ON *.* FROM 'user'@'host';后,必须查mysql.user表确认File_priv = 'N',尤其注意匿名用户(''@'localhost')和测试账号 -
secure_file_priv = NULL必须写在[mysqld]段并重启,设为空字符串''是最危险配置 -
skip_symbolic_links = yes(不是symbolic-links = 0)才是 MySQL 8.0.33+ 唯一有效的符号链接禁用项 - 最终验证命令:
SELECT @@local_infile, @@secure_file_priv, @@skip_symbolic_links;应分别返回0、NULL、1
最容易被忽略的点是:云数据库(如 RDS)或容器环境里,参数组修改可能未实时生效,docker run 命令中传入的 --local-infile 会覆盖配置文件,而这些都很难从外部感知——上线前必须亲手连上去跑验证 SQL,不能只信文档或控制台开关。


















