必须服务端和客户端双侧禁用load data local infile,仅关一端无效;服务端需在my.cnf的[mysqld]段设local_infile=off并重启,客户端须在连接时显式禁用(如pymysql设local_infile=false),且均需验证select @@local_infile返回0。

必须服务端和客户端双侧禁用,只关一端等于没关。 MySQL 的 LOAD DATA LOCAL INFILE 不是服务端读文件,而是服务端“指挥”客户端读本地磁盘——攻击者一旦有 SQL 注入点,就能让 PHP、Python 或 Navicat 这类客户端把 /etc/passwd 或 ~/.my.cnf 发送给数据库,再通过报错或盲注回显。这不是理论风险,是真实被利用过的链路。
服务端 local_infile=OFF 必须写进配置并重启
仅执行 SET GLOBAL local_infile = OFF 是临时的,MySQL 重启后恢复默认(8.0.22+ 默认 OFF,但旧版本或某些 Docker 镜像仍默认 ON)。真正生效要改配置文件:
- 编辑
/etc/my.cnf或/etc/mysql/mysql.conf.d/mysqld.cnf,在[mysqld]段下添加:local_infile = OFF
- 确认没有其他配置覆盖它(比如
[server]或[mysqld_safe]段里又写了local_infile = ON) - 重启服务:
sudo systemctl restart mysql(或mariadb) - 连上后验证:
SELECT @@local_infile;返回0才算落地
客户端连接时必须显式禁用 local_infile
服务端关了,客户端还开着,照样能发请求——只是服务端会拒收,但攻击面仍在。不同客户端禁用方式差异大,漏掉任意一个都可能留后门:
- MySQL CLI:启动时加
--local-infile=0;或在~/.my.cnf的[client]段写local_infile = 0 - Python + PyMySQL:
pymysql.connect(..., local_infile=False)(注意不是False默认值,部分二进制包默认裁剪支持) - Python + 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 })
云环境与容器里最容易漏掉验证
RDS(如阿里云、AWS)、Docker 官方镜像、K8s initContainer 中的 MySQL,常默认开启 local_infile,或配置写了但没生效也没检查。更麻烦的是:有些托管服务不让你改 my.cnf,只能通过参数组(Parameter Group)设置,而参数组修改后不一定实时应用,也不提示是否成功。
- 上线前必须手动连上去跑
SELECT @@local_infile;,不能只信文档或控制台开关 - Docker 启动命令里如果带了
--local-infile,会覆盖配置文件,优先级更高 - K8s 中若用
initContainer初始化配置,要确认它写的my.cnf确实被主容器加载(比如挂载路径是否正确、权限是否允许读) - 某些运维脚本用
mysql --defaults-file=/tmp/evil.cnf启动,可绕过全局配置——生产环境应禁止非白名单用户执行mysql命令
真正难防的不是配置本身,而是“以为关了”的错觉。很多团队改完服务端配置就结束,却没碰应用代码里的 PyMySQL 连接参数,也没查 CI/CD 流水线里跑的测试是否用了带 local_infile=True 的临时连接。安全闭环只存在于每一次连接建立的那一刻,而不是某次部署操作。











