Restrict MySQL file privileges
/etc/mysql/mysql.conf.d/mysqld.cnf[mysqld]secure_file_priv = NULLlocal_infile = 0secure_file_priv = NULL disables file import and export entirely. That is
the setting you want unless you have a specific, current need for it.
sudo systemctl restart mysqlWhy it matters
Section titled “Why it matters”The FILE privilege lets an account read and write files as the mysql OS
user:
SELECT LOAD_FILE('/etc/passwd');SELECT '<?php system($_GET[0]); ?>' INTO OUTFILE '/var/www/html/x.php';That is a SQL injection turning into file disclosure, and then into a web shell if the database user can write anywhere the web server serves. It is the standard escalation path from “SQL injection” to “server compromise”, and it is why keeping FILE away from applications is on this list.
secure_file_priv is the backstop for when a grant is wrong:
| Value | Effect |
|---|---|
NULL |
Import and export disabled entirely — what you want |
/some/dir |
Restricted to that directory |
| (empty) | No restriction — anywhere the mysql user can reach |
An empty value is the dangerous one, and it was the historical default. Packaged
installs generally set it to /var/lib/mysql-files/, which is a reasonable
middle ground — but NULL is better if nothing uses it, and on most servers
nothing does.
local_infile runs in the direction you don’t expect
Section titled “local_infile runs in the direction you don’t expect”LOAD DATA LOCAL INFILE reads a file from the client and sends it to the
server. So the obvious reading is “a user could load their own files” — which is
not much of a risk, since they’re their own files.
The real attack goes the other way, and it’s worth understanding because it changes what you protect:
A malicious or compromised MySQL server can ask a connecting client to send it
any file. The protocol lets the server respond to a query with “send me this
path”, and a client with local_infile enabled complies. Point a database admin
tool at a rogue server and it will hand over /etc/passwd, or the client’s SSH
keys, or the application config — from the machine running the client.
That reframes the control: local_infile = 0 on your server closes your server
as a participant in that attack. It does not protect your clients connecting
elsewhere — that’s a client-side setting. If you run tooling that connects to
databases you don’t control, disable local infile in the client too.
This is why the page is medium and not higher: the server-side setting closes a
narrow thing. The secure_file_priv half is the part that protects you.
Related
Section titled “Related”- Grant least privilege — don’t grant FILE in the first place.
- MySQL exposed to the internet — where this escalation ends.