SEC.CFG.SECURE_FILE_PRIV — File I/O has no limit, and the value that says so looks like nothing
- Category: security
- Severity: high
- Level: 0
- Confidence: deterministic
- Downtime class: none — the finding is about a server setting, not about a statement
- Stability: stable
- Suites: audit
- Applies to: MySQL 8.4
Read this table before the rest of the page
secure_file_priv decides how far the FILE privilege reaches. It has three states, and MySQL
spells them in the way most likely to be misread:
| The server reports | It means | What it looks like |
|---|---|---|
NULL — the four-character string | file I/O is switched off — the safest state | "some value is set, so probably fine" |
| the empty value | no limit at all — the most dangerous state | "nothing is set, so it does not matter" |
| a path | confined to that directory | correct |
The two ends of the scale are spelled the opposite way round from the intuition. An is_null(), an
empty() or a ?: 'unset' anywhere in a script that reads this variable swaps them — and reports
the hardened server as the one worth ignoring.
What the unrestricted state actually allows
LOAD_FILE() reads any file the MySQL server process can read. SELECT … INTO OUTFILE writes one
anywhere it can write. Both run as the server's operating-system user, not as the account
connecting.
On its own that grants nobody anything — the operations need the FILE privilege, which
SEC.PRIV.GRANT_FILE reports separately. Together they are the difference
between a database problem and a problem on the host.
Why this is high and not critical
It is a multiplier on a privilege, not a privilege. Without an account holding FILE, an
unrestricted secure_file_priv changes nothing. Reporting it at the top of the axis would put it
beside findings that need nothing else to be true.
Two findings, not one
The grant and the setting are reported separately on purpose. They are fixed in different places by different people — one by whoever manages accounts, one by whoever manages the server — and either one alone is already worth acting on. Folding them into a single alarm would mean neither could be closed without the other.
What it looks like
# my.cnf: no limit at all on where FILE may read and write.
# (MySQL reports this state as the EMPTY value.)
secure_file_priv =
What to do about it
# Switch the operations off entirely. MySQL reports this as the string NULL.
secure_file_priv = NULL
# …or confine them to one directory, if an import job genuinely needs them.
secure_file_priv = /var/lib/mysql-files
A directory is a deliberate limit and is never reported. Neither is the disabled state. The only
finding is the empty value — so this rule is silent on every server somebody has actually
configured, and a data-import job using LOAD DATA INFILE keeps working with a directory set.
The variable is read-only at run time: it moves in my.cnf and takes a restart.