必须同时禁用服务端local_infile和客户端local_infile,否则SQL注入可利用客户端驱动读取/etc/passwd等本地文件;secure_file_priv与FILE权限仅管控服务端读取,与LOCAL无关。

只关服务端 local_infile = OFF,攻击者仍能通过 SQL 注入读取客户端的 /etc/passwd 或 ~/.my.cnf
LOAD DATA LOCAL INFILE 是客户端主动读文件,不是服务端读
很多人误以为这是 MySQL 服务器在读本地磁盘,其实整个流程是:服务端发指令 → 客户端进程(如 PHP 的 mysqli、Python 的 pymysql)用自己的权限打开并读取本地路径 → 把内容发回服务端。这意味着:
- 服务端 local_infile = OFF 只会让它收到请求后报
ERROR 1148,但客户端早已暴露了可读路径 - 只要客户端没禁用(比如 pymysql.connect(..., local_infile=True)),SQL 注入就能触发任意本地文件上传
- 攻击者不需要登录数据库,只要控制一条 SQL(如注入到 WHERE 子句)就能发起读取
客户端驱动默认行为不统一,漏一个就破防
不同连接库对 local_infile 的默认值和启用方式差异极大,不能依赖“服务端开了/关了”来推断实际行为:
-
mysql命令行默认开启,必须显式加--local-infile=0或在~/.my.cnf的[client]段写local_infile=0 -
pymysql默认禁用,但一旦传了local_infile=True就立刻生效;漏掉参数 ≠ 安全,而是继承驱动默认(旧版可能默认 True) -
mysql-connector-python8.0.23+ 默认allow_local_infile=False,但低版本默认为True,且 URL 参数&allowLoadLocalInfile=true可覆盖 -
PHP mysqli默认关闭,但mysqli_options($conn, MYSQLI_OPT_LOCAL_INFILE, true)一行就能打开,且常被封装在 ORM 初始化逻辑里
验证是否真禁用,不能只看 @@local_infile
执行 SELECT @@local_infile; 返回 0 只说明服务端变量关了,不代表客户端通道已堵死。必须实测:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 用目标客户端(如应用使用的 pymysql 实例)执行
LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE t - 预期结果是
ERROR 1148 (42000)—— 这代表客户端侧拒绝发起请求 - 如果报
ERROR 2068或权限错误,说明客户端发出了请求、服务端拦下了,仍存在信息泄露风险(比如路径是否存在) - 特别注意 Docker/K8s 场景:容器内 mysql-client 包可能自带默认开启,与宿主机配置无关
secure_file_priv 和 FILE 权限是另一层,别混为一谈
local_infile 管的是客户端读本地文件,而 secure_file_priv 和 FILE 权限管的是服务端读服务器磁盘。两者完全正交:
- 即使
local_infile = OFF,只要用户有FILE权限 +secure_file_priv指向非空目录,就能用LOAD DATA INFILE读取 MySQL 服务器上的/var/lib/mysql-files/my.cnf等敏感文件 - 所以生产环境必须同步做:
REVOKE FILE ON *.* FROM 'user'@'host';+secure_file_priv = NULL(MySQL 8.0.17+)或''(旧版) - 检查残留命令:
SELECT User,Host,File_priv FROM mysql.user WHERE File_priv='Y';
真正安全的边界不在服务端变量,而在每个客户端连接初始化时是否明确传递了禁用标志——这一步最容易被 ORM 封装、Docker 镜像、历史脚本绕过。










