MySQL 需要读写自身的数据库文件,但大多数应用数据库并不需要通过 SQL 语句与操作系统交换任意文件。如果一台数据库服务器主要用于 WordPress 或其他 Web 应用,那么启用这些并未使用的文件传输功能,只会增加不必要的攻击面。
下面两个 MySQL 配置可以提供一层非常实用的安全保护:
[mysqld]
local_infile=0
secure_file_priv=NULL
它们保护的是两条不同的文件访问路径。local_infile=0 禁止从数据库客户端所在的机器加载文件,而 secure_file_priv=NULL 则禁止通过相关 SQL 操作直接读写 MySQL 服务器上的文件系统。两者的安全目标有所重叠,但不能互相替代。
“Local”的两种含义
这里的术语很容易令人困惑,因为 LOCAL 指的是 MySQL 客户端所在的机器,而不是数据库服务器本身。
对比下面两条 SQL 语句:
LOAD DATA INFILE '/path/data.csv' INTO TABLE records;
LOAD DATA LOCAL INFILE '/path/data.csv' INTO TABLE records;
不带 LOCAL 时,MySQL 服务器进程会打开数据库服务器上的文件;带有 LOCAL 时,则由客户端程序打开本地文件,再把文件内容传送给 MySQL 服务器。
这一区别也解释了为什么两个配置都需要启用:
| 配置 | 保护的文件位置 | 主要影响的操作 |
|---|---|---|
local_infile=0 |
客户端主机,可能是应用服务器或 Web 服务器 | LOAD DATA LOCAL INFILE |
secure_file_priv=NULL |
MySQL 数据库服务器 | LOAD DATA INFILE、SELECT ... INTO OUTFILE、SELECT ... INTO DUMPFILE 和 LOAD_FILE() |
为什么要禁用 LOAD DATA LOCAL INFILE?
管理员需要导入大型 CSV 文件时,LOAD DATA LOCAL INFILE 确实非常方便。不过,这项功能也跨越了一条重要的安全边界:MySQL 服务器会要求已连接的客户端读取本地文件,并把文件内容传送到服务器。
MySQL 官方文档指出了两个主要风险。首先,恶意或经过修改的数据库服务器可能要求客户端发送另一个文件,而不是客户端原本准备导入的文件。其次,在 Web 环境中,MySQL 的“客户端”通常实际上是 Web 服务器进程,因此可能受到威胁的是该进程有权读取的所有文件,而不只是管理员电脑上的文件。
还有一个原因需要谨慎对待 LOCAL:执行 LOAD DATA LOCAL 并不要求数据库账户拥有 MySQL 的全局 FILE 权限。它主要取决于服务器和客户端是否同时允许 LOCAL 功能。因此,在服务器端禁用它,可以建立一道明确而统一的安全边界:
local_infile=0
设置以后,即使某个客户端库或命令行客户端启用了本地文件加载,服务器仍然会拒绝执行 LOAD DATA LOCAL INFILE。WordPress 数据库通常完全不需要这项功能,因此将其禁用是成本很低的最小权限实践。
为什么要设置 secure_file_priv=NULL?
secure_file_priv 变量控制可以直接与数据库服务器文件系统交互的 SQL 操作。在 MySQL 中,它的三种取值具有完全不同的安全含义:
| 取值 | 行为 |
|---|---|
| 空字符串 | 在数据库权限和操作系统权限允许的范围内,不限制文件导入和导出;MySQL 官方明确将其描述为不安全的配置 |
| 指定目录 | 只允许在指定目录中进行文件操作 |
NULL |
完全禁用相关的文件导入和导出操作 |
如果应用数据库从来不需要服务器端的文件导入或导出,那么最严格的设置是:
secure_file_priv=NULL
这一配置会阻止相关 SQL 功能让 MySQL 服务器进程读取其能够访问的文件,或者把查询结果写入服务器文件系统。这个限制非常重要,因为数据库层面的入侵一旦获得文件系统读写能力,就可能进一步演变成范围更大的服务器安全事件。例如,不必要的文件写入能力可能让数据被写入本不应该出现的位置;不必要的文件读取能力则可能泄露 MySQL 操作系统账户能够访问的配置文件或应用数据。
这项设置作为纵深防御措施尤其有价值。正常的应用数据库账户原本就不应该拥有全局 FILE 权限,但配置错误可能发生,凭据可能被滥用,软件漏洞也可能让攻击者执行非预期的 SQL。即使管理员不慎授予了 FILE 权限,secure_file_priv=NULL 仍然可以提供一道服务器级别的最后防线。
为什么两个设置缺一不可?
人们很容易误以为 secure_file_priv=NULL 能够禁止所有形式的文件加载,但它并不能保护客户端一侧的 LOCAL 路径。使用 LOCAL 时,读取源文件的是客户端,而不是 MySQL 服务器,因此服务器端的 FILE 权限和 secure_file_priv 限制并不控制这次读取操作。
反过来,local_infile=0 只会禁用带有 LOCAL 的加载方式。它本身无法阻止拥有足够权限的数据库账户使用 SELECT ... INTO OUTFILE 等服务器端文件操作。
因此,这两个配置分别关闭了两条互补的文件访问路径:
客户端文件系统 --X--> MySQL local_infile=0
MySQL --X--> 服务器文件系统 secure_file_priv=NULL
两者配合使用,可以建立一条简单清晰的安全策略:应用数据只能通过预期的数据库查询和经过批准的备份工具进出数据库,而不能通过通用 SQL 文件操作随意读写文件。
配置和验证
在常见的 Debian 或 Ubuntu 系统中,可以把下面的配置添加到 MySQL 服务器配置文件的 [mysqld] 部分。该文件通常位于 /etc/mysql/mysql.conf.d/mysqld.cnf:
[mysqld]
local_infile=0
secure_file_priv=NULL
由于 secure_file_priv 只能在 MySQL 启动时设置,因此修改配置后需要重启 MySQL:
sudo systemctl restart mysql
重启后应当检查实际生效的变量值,而不能只是假设 MySQL 已经读取了预期的配置文件:
SHOW GLOBAL VARIABLES LIKE 'local_infile';
SHOW GLOBAL VARIABLES LIKE 'secure_file_priv';
预期结果应该是 local_infile 的值为 OFF,secure_file_priv 的值为 NULL。
重启之后最好再检查一下 MySQL 错误日志。如果配置项放错了位置、存在重复设置、使用了无效值,或者多个配置文件的加载顺序与预期不同,最终生效的安全策略都可能与你写入的配置不同。
哪些功能可能会受到影响?
只有在正常业务确实不需要相关功能时,才适合采用这些设置。
local_infile=0 会使依赖 LOAD DATA LOCAL INFILE 的工具或脚本无法工作,其中可能包括部分批量 CSV 导入工具和 MySQL Shell 导入流程。secure_file_priv=NULL 会阻止服务器端文件导入和导出,也会影响 mysqldump --tab 之类依赖数据库服务器创建文件的工作方式。
下面这种常规备份方式不会受到影响,因为数据库记录通过正常的客户端协议传输,再由 Shell 把输出内容写入文件:
mysqldump --single-transaction database_name > backup.sql
如果确实需要服务器端导入或导出文件,把 secure_file_priv 限制到一个专用目录,要比将它设置为空字符串安全得多:
secure_file_priv=/var/lib/mysql-files/
这个目录必须预先创建,并应配置严格的所有权和访问权限。目录中不应包含任何应用程序代码或敏感配置。只启用真正需要的功能,将启用时间控制在尽可能短的范围内,并在操作完成后重新禁用。
这些设置不能解决哪些问题?
这两个选项只是安全防护措施,并不能代替完整的数据库安全体系。它们无法防止 SQL 注入,无法阻止攻击者通过普通 SELECT 查询读取数据库记录,也无法保护已经泄露的凭据,更无法限制已经获得操作系统访问权限的进程。它们同样无法弥补应用数据库账户权限过大的问题。
一套合理的数据库安全基线还应该包括:
- 为每个应用创建独立的数据库账户;
- 只授予应用实际需要的数据库和数据表权限;
- 确保应用账户没有全局
FILE权限; - 使用参数化查询,并及时更新应用程序、插件和 MySQL;
- 限制数据库的网络访问范围;
- 使用低权限的操作系统账户运行 MySQL;
- 通过合理的文件系统权限保护配置文件、备份文件和数据库文件;
- 定期测试备份,并监控备份任务是否正常完成。
总结
安全加固并不总是意味着添加复杂的控制措施。很多时候,更有效的方法是直接关闭业务从未使用的功能。典型的 WordPress 或 Web 应用数据库通常没有理由接受客户端本地文件加载,也不需要允许 SQL 语句直接访问数据库服务器的文件系统。
设置 local_infile=0 和 secure_file_priv=NULL,可以在基本不影响正常应用查询和常规逻辑备份的情况下关闭这两条文件访问路径。这并不会让数据库变得绝对安全,但它能够缩小攻击面,并在其他安全措施失效时提供一道更坚固的最后防线——这正是纵深防御的价值所在。
参考资料
兼容性说明:不同数据库产品和版本接受的配置值及默认行为可能不同。本文所介绍的
secure_file_priv=NULL行为针对 MySQL。将相同配置应用到 MariaDB 或其他兼容 MySQL 的数据库之前,应先查阅对应产品的文档,并检查配置实际生效后的变量值。
英文:Hardening MySQL File Access with local_infile=0 and secure_file_priv=NULL
- 使用 local_infile=0 和 secure_file_priv=NULL 加固 MySQL 文件访问安全
MySQL 的文件导入和导出功能虽然方便… - MySQL参数一键配置脚本: 有效提升数据库性能
我一直是自己租用VPS服务器,然后搭建各… - 把 MySQL 中的 MyISAM 表格转换成 InnoDB 的PHP小工具
我们都知道 MYSQL中常见表格的引擎有… - 怎么样通过 LinqPad 连接到 SBDS – MySQL?
我一直用的是 LinqPad 来连接 @… - [机器学习] 用 MySQL 来演示 KNN算法
机器学习这几年越来越火, 特别是相关算法…
上一篇: 误删后通过WinFr来恢复SSD固态硬盘的数据