Configuración de Seguridad MySQL/MariaDB: Guía Completa de Hardening

Introducción

La seguridad de las bases de datos es primordial en el panorama actual de amenazas donde las violaciones de datos y los ciberataques son cada vez más comunes. MySQL y MariaDB, aunque son sistemas de bases de datos potentes y ampliamente utilizados, requieren configuración de seguridad adecuada para proteger datos sensibles de acceso no autorizado, ataques de inyección SQL y otras amenazas de seguridad.

Una base de datos comprometida puede llevar a consecuencias catastróficas incluyendo robo de datos, pérdidas financieras, sanciones regulatorias y daño severo a la reputación. Según informes de la industria, el costo promedio de una violación de datos excede los $4 millones, haciendo de la seguridad de bases de datos no solo un requisito técnico sino un imperativo de negocio.

Esta guía completa proporciona instrucciones detalladas para endurecer instalaciones de MySQL y MariaDB, implementar estrategias de defensa en profundidad y seguir mejores prácticas de la industria para seguridad de bases de datos. Ya sea que estés gestionando un entorno de desarrollo o un sistema de producción que maneja datos sensibles de clientes, esta guía te ayudará a establecer medidas de seguridad robustas.

Amenazas de Seguridad a Sistemas de Bases de Datos

Las amenazas comunes incluyen:

  • Inyección SQL: Inyección de código SQL malicioso a través de vulnerabilidades de aplicación
  • Ataques de Fuerza Bruta: Intentos automatizados de adivinación de contraseñas
  • Escalación de Privilegios: Explotación de vulnerabilidades para obtener acceso elevado
  • Exposición de Datos: Acceso no autorizado a datos por mala configuración
  • Ataques Man-in-the-Middle: Interceptación de tráfico de base de datos no cifrado
  • Amenazas Internas: Acciones maliciosas o negligentes de usuarios autorizados

Requisitos Previos

Antes de proceder con la configuración de seguridad:

  • MySQL o MariaDB ya instalado y ejecutándose
  • Acceso root o sudo al servidor
  • Contraseña root de base de datos disponible
  • Comprensión básica de comandos SQL
  • Backup de configuración de base de datos existente
  • Comprensión de los requisitos de base de datos de tu aplicación

Creación de Backup de Configuración

# Backup de configuración MySQL
sudo cp /etc/mysql/mysql.conf.d/mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf.backup

# Backup de configuración MariaDB
sudo cp /etc/mysql/mariadb.conf.d/50-server.cnf /etc/mysql/mariadb.conf.d/50-server.cnf.backup

# Backup de todos los directorios MySQL
sudo cp -r /etc/mysql /etc/mysql.backup.$(date +%Y%m%d)

# Backup de bases de datos antes de cambios de seguridad
sudo mysqldump --all-databases > /backup/all_databases_pre_security_$(date +%Y%m%d).sql

Hardening de Seguridad Inicial

Ejecutar mysql_secure_installation

El script mysql_secure_installation proporciona mejoras de seguridad esenciales:

# Ejecutar script de seguridad
sudo mysql_secure_installation

Prompts del script y respuestas recomendadas:

Would you like to setup VALIDATE PASSWORD component? [Y/n]
Respuesta: Y (Sí - habilita validación de fortaleza de contraseña)

There are three levels of password validation policy:
LOW    Length >= 8
MEDIUM Length >= 8, numeric, mixed case, and special characters
STRONG Length >= 8, numeric, mixed case, special characters and dictionary file
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG:
Respuesta: 2 (STRONG - para entornos de producción)

Change the password for root? [Y/n]
Respuesta: Y (si quieres actualizar contraseña root)

Remove anonymous users? [Y/n]
Respuesta: Y (usuarios anónimos son riesgos de seguridad)

Disallow root login remotely? [Y/n]
Respuesta: Y (root solo debería iniciar sesión localmente)

Remove test database and access to it? [Y/n]
Respuesta: Y (base de datos test es innecesaria en producción)

Reload privilege tables now? [Y/n]
Respuesta: Y (aplicar cambios inmediatamente)

Verificar Resultados del Script de Seguridad

# Iniciar sesión en MySQL
sudo mysql -u root -p

# Verificar usuarios anónimos (debería retornar vacío)
SELECT User, Host FROM mysql.user WHERE User = '';

# Verificar base de datos test (no debería existir)
SHOW DATABASES LIKE 'test';

# Verificar restricciones de host del usuario root
SELECT User, Host FROM mysql.user WHERE User = 'root';

# Salir de MySQL
EXIT;

Gestión de Usuarios y Control de Acceso

Comprender el Sistema de Privilegios de MySQL

MySQL usa un sistema de privilegios jerárquico:

  1. Privilegios globales: Se aplican a todas las bases de datos
  2. Privilegios de base de datos: Se aplican a base de datos específica
  3. Privilegios de tabla: Se aplican a tablas específicas
  4. Privilegios de columna: Se aplican a columnas específicas
  5. Privilegios de rutina: Se aplican a procedimientos almacenados y funciones

Crear Usuarios con Privilegio Mínimo

Nunca usar root para aplicaciones. Crear usuarios dedicados con privilegios mínimos requeridos:

-- Conectar como root
sudo mysql -u root -p

-- Crear usuario para base de datos específica
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'StrongP@ssw0rd123!';

-- Crear base de datos
CREATE DATABASE myapp_production CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- Otorgar solo privilegios específicos
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_production.* TO 'app_user'@'localhost';

-- Aplicar cambios
FLUSH PRIVILEGES;

-- Verificar privilegios de usuario
SHOW GRANTS FOR 'app_user'@'localhost';

Directrices de Privilegios:

-- Aplicación web (requisitos típicos)
GRANT SELECT, INSERT, UPDATE, DELETE ON database.* TO 'webapp_user'@'localhost';

-- Usuario de solo lectura para reportes
GRANT SELECT ON database.* TO 'report_user'@'localhost';

-- Usuario de backup (necesita privilegios específicos)
GRANT SELECT, RELOAD, SHOW DATABASES, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'backup_user'@'localhost';

-- Usuario desarrollador (limitado a base de datos de desarrollo)
GRANT ALL PRIVILEGES ON dev_database.* TO 'developer'@'localhost';

-- Usuario de monitoreo (acceso de solo lectura al sistema)
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'monitor_user'@'localhost';

Restricciones de Acceso Basadas en Host

Restringir usuarios a hosts o direcciones IP específicas:

-- Usuario solo puede conectar desde localhost
CREATE USER 'local_user'@'localhost' IDENTIFIED BY 'password';

-- Usuario solo puede conectar desde IP específica
CREATE USER 'remote_user'@'192.168.1.50' IDENTIFIED BY 'password';

-- Usuario puede conectar desde subred específica
CREATE USER 'subnet_user'@'192.168.1.%' IDENTIFIED BY 'password';

-- Usuario puede conectar desde dominio específico
CREATE USER 'domain_user'@'%.example.com' IDENTIFIED BY 'password';

-- Evitar usar comodín % para producción
-- MAL: CREATE USER 'user'@'%' IDENTIFIED BY 'password';

Implementar Políticas de Contraseñas

Para MySQL 8.0+:

-- Instalar componente de validación de contraseña
INSTALL COMPONENT 'file://component_validate_password';

-- Configurar política de contraseña
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 12;
SET GLOBAL validate_password.mixed_case_count = 1;
SET GLOBAL validate_password.number_count = 1;
SET GLOBAL validate_password.special_char_count = 1;
SET GLOBAL validate_password.check_user_name = ON;

-- Ver política actual
SHOW VARIABLES LIKE 'validate_password%';

-- Hacer configuraciones permanentes en my.cnf
[mysqld]
validate_password.policy=STRONG
validate_password.length=12
validate_password.mixed_case_count=1
validate_password.number_count=1
validate_password.special_char_count=1

Para MariaDB:

-- Instalar plugin de validación de contraseña
INSTALL SONAME 'simple_password_check';

-- Configurar requisitos de contraseña
SET GLOBAL simple_password_check_minimal_length = 12;
SET GLOBAL simple_password_check_digits = 1;
SET GLOBAL simple_password_check_letters_same_case = 1;
SET GLOBAL simple_password_check_other_characters = 1;

-- Agregar a archivo de configuración
[mariadb]
plugin_load_add = simple_password_check
simple_password_check_minimal_length = 12
simple_password_check_digits = 1
simple_password_check_letters_same_case = 1
simple_password_check_other_characters = 1

Expiración y Rotación de Contraseñas

Implementar políticas de expiración de contraseñas:

-- Establecer expiración global de contraseña (días)
SET GLOBAL default_password_lifetime = 90;

-- Establecer expiración de contraseña para usuario específico
ALTER USER 'app_user'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;

-- Requerir cambio inmediato de contraseña
ALTER USER 'user'@'localhost' PASSWORD EXPIRE;

-- Nunca expirar contraseña (usar con moderación)
ALTER USER 'service_account'@'localhost' PASSWORD EXPIRE NEVER;

-- Ver estado de expiración de contraseña
SELECT User, Host, password_expired, password_lifetime, password_last_changed
FROM mysql.user;

Bloqueo de Cuenta

Bloquear y desbloquear cuentas de usuario:

-- Bloquear cuenta de usuario
ALTER USER 'suspicious_user'@'localhost' ACCOUNT LOCK;

-- Desbloquear cuenta de usuario
ALTER USER 'user'@'localhost' ACCOUNT UNLOCK;

-- Crear usuario con cuenta bloqueada
CREATE USER 'inactive_user'@'localhost' IDENTIFIED BY 'password' ACCOUNT LOCK;

-- Ver cuentas bloqueadas
SELECT User, Host, account_locked FROM mysql.user WHERE account_locked = 'Y';

Seguimiento de Intentos de Inicio de Sesión Fallidos (MySQL 8.0.19+)

-- Configurar seguimiento de intentos de inicio de sesión fallidos
CREATE USER 'user'@'localhost'
IDENTIFIED BY 'password'
FAILED_LOGIN_ATTEMPTS 3
PASSWORD_LOCK_TIME 2;  -- Bloquear por 2 días

-- O usar UNBOUNDED para bloqueo permanente
ALTER USER 'user'@'localhost'
FAILED_LOGIN_ATTEMPTS 5
PASSWORD_LOCK_TIME UNBOUNDED;

-- Ver configuración de inicio de sesión fallido
SELECT User, Host, User_attributes
FROM mysql.user
WHERE JSON_EXTRACT(User_attributes, '$.Password_locking') IS NOT NULL;

Seguridad de Red

Vinculación a Interfaces Específicas

Configurar MySQL para escuchar solo en interfaces de red específicas:

# Editar archivo de configuración
# /etc/mysql/mysql.conf.d/mysqld.cnf (Ubuntu/Debian)
# /etc/my.cnf (CentOS/Rocky)

[mysqld]
# Escuchar solo en localhost (más seguro)
bind-address = 127.0.0.1

# Escuchar en IP específica
bind-address = 192.168.1.100

# Escuchar en múltiples IPs (MySQL 8.0.13+)
bind-address = 127.0.0.1,192.168.1.100

# Para MariaDB 10.3.3+
bind-address = 0.0.0.0  # Escuchar en todas las interfaces
# Luego usar firewall para restringir acceso

Reiniciar MySQL:

sudo systemctl restart mysql

# Verificar vinculación
sudo ss -tulpn | grep 3306
sudo netstat -tulpn | grep 3306

Deshabilitar Resolución de Nombres

Saltar búsquedas de nombres de host DNS para prevenir suplantación DNS:

[mysqld]
skip-name-resolve

Esto fuerza a MySQL a usar solo direcciones IP. Actualizar permisos en consecuencia:

-- Antes: otorgar a nombre de host
GRANT ALL ON database.* TO 'user'@'server.example.com';

-- Después: otorgar a IP
GRANT ALL ON database.* TO 'user'@'192.168.1.100';

-- Revocar permisos basados en nombre de host
REVOKE ALL ON database.* FROM 'user'@'server.example.com';
DROP USER 'user'@'server.example.com';

Reiniciar MySQL y verificar:

sudo systemctl restart mysql

# Verificar configuración
mysql -u root -p -e "SHOW VARIABLES LIKE 'skip_name_resolve';"

Cambiar Puerto Predeterminado

Cambiar puerto de MySQL para reducir superficie de ataque automatizado:

[mysqld]
port = 33306  # Puerto personalizado en lugar de 3306

Actualizar reglas de firewall:

# UFW
sudo ufw delete allow 3306/tcp
sudo ufw allow 33306/tcp

# firewalld
sudo firewall-cmd --permanent --remove-service=mysql
sudo firewall-cmd --permanent --add-port=33306/tcp
sudo firewall-cmd --reload

Reiniciar MySQL y verificar:

sudo systemctl restart mysql

# Conectar con puerto personalizado
mysql -u root -p -P 33306

# Verificar puerto
sudo ss -tulpn | grep mysql

Cifrado SSL/TLS

Generar Certificados SSL

Crear certificados SSL para conexiones cifradas:

# Crear directorio para certificados
sudo mkdir -p /etc/mysql/ssl
cd /etc/mysql/ssl

# Generar clave y certificado de CA
sudo openssl genrsa 2048 > ca-key.pem
sudo openssl req -new -x509 -nodes -days 3650 \
    -key ca-key.pem \
    -out ca-cert.pem \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=MySQL-CA"

# Generar solicitud de certificado de servidor y clave
sudo openssl req -newkey rsa:2048 -days 3650 -nodes \
    -keyout server-key.pem \
    -out server-req.pem \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=MySQL-Server"

# Procesar clave RSA de servidor
sudo openssl rsa -in server-key.pem -out server-key.pem

# Firmar certificado de servidor
sudo openssl x509 -req -in server-req.pem -days 3650 \
    -CA ca-cert.pem \
    -CAkey ca-key.pem \
    -set_serial 01 \
    -out server-cert.pem

# Generar solicitud de certificado de cliente y clave
sudo openssl req -newkey rsa:2048 -days 3650 -nodes \
    -keyout client-key.pem \
    -out client-req.pem \
    -subj "/C=US/ST=State/L=City/O=Organization/CN=MySQL-Client"

# Procesar clave RSA de cliente
sudo openssl rsa -in client-key.pem -out client-key.pem

# Firmar certificado de cliente
sudo openssl x509 -req -in client-req.pem -days 3650 \
    -CA ca-cert.pem \
    -CAkey ca-key.pem \
    -set_serial 02 \
    -out client-cert.pem

# Verificar certificados
sudo openssl verify -CAfile ca-cert.pem server-cert.pem client-cert.pem

# Establecer permisos apropiados
sudo chown mysql:mysql /etc/mysql/ssl/*
sudo chmod 600 /etc/mysql/ssl/*-key.pem
sudo chmod 644 /etc/mysql/ssl/*-cert.pem

Configurar MySQL para SSL

Editar configuración de MySQL:

[mysqld]
# Configuración SSL
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem

# Requerir SSL para todas las conexiones (opcional pero recomendado)
require_secure_transport=ON

# Versiones TLS (MySQL 8.0+)
tls_version=TLSv1.2,TLSv1.3

# Configuración de cipher
ssl-cipher=ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384

Reiniciar MySQL:

sudo systemctl restart mysql

# Verificar que SSL está habilitado
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"

# Verificar estado SSL
mysql -u root -p -e "STATUS" | grep SSL

Requerir SSL para Usuarios

Forzar usuarios específicos a usar SSL:

-- Requerir SSL para usuario existente
ALTER USER 'app_user'@'%' REQUIRE SSL;

-- Crear usuario con requisito SSL
CREATE USER 'secure_user'@'%' IDENTIFIED BY 'password' REQUIRE SSL;

-- Requerir cipher SSL específico
ALTER USER 'app_user'@'%' REQUIRE CIPHER 'ECDHE-RSA-AES256-GCM-SHA384';

-- Requerir certificado de cliente válido
ALTER USER 'app_user'@'%' REQUIRE X509;

-- Requerir subject de certificado específico
ALTER USER 'app_user'@'%' REQUIRE SUBJECT '/C=US/ST=State/CN=MySQL-Client';

-- Requerir emisor de certificado específico
ALTER USER 'app_user'@'%' REQUIRE ISSUER '/C=US/ST=State/CN=MySQL-CA';

-- Ver requisitos SSL de usuario
SELECT User, Host, ssl_type, ssl_cipher, x509_issuer, x509_subject
FROM mysql.user
WHERE User = 'app_user';

-- Eliminar requisito SSL
ALTER USER 'user'@'host' REQUIRE NONE;

Conectar con SSL

# Conectar con SSL
mysql -u app_user -p --ssl-mode=REQUIRED

# Conectar con certificado de cliente
mysql -u app_user -p \
    --ssl-ca=/etc/mysql/ssl/ca-cert.pem \
    --ssl-cert=/etc/mysql/ssl/client-cert.pem \
    --ssl-key=/etc/mysql/ssl/client-key.pem

# Verificar conexión SSL
mysql> \s
# Buscar: SSL: Cipher in use

# O consultar
mysql> SHOW STATUS LIKE 'Ssl_cipher';

Seguridad del Sistema de Archivos

Asegurar Archivos de Configuración

Proteger archivos de configuración de MySQL:

# Establecer permisos seguros en archivos de configuración
sudo chmod 644 /etc/mysql/my.cnf
sudo chmod 644 /etc/mysql/mysql.conf.d/mysqld.cnf
sudo chown root:root /etc/mysql/my.cnf

# Si se almacenan contraseñas en archivo de configuración (no recomendado)
sudo chmod 600 /etc/mysql/my.cnf

Asegurar Directorio de Datos

Proteger directorio de datos de MySQL:

# Establecer propiedad adecuada
sudo chown -R mysql:mysql /var/lib/mysql

# Establecer permisos seguros
sudo chmod 700 /var/lib/mysql
sudo chmod 660 /var/lib/mysql/*

# Verificar permisos
ls -la /var/lib/mysql/

Proteger Binary Logs

Asegurar binary logs de MySQL:

# Establecer propiedad
sudo chown mysql:mysql /var/lib/mysql/mysql-bin.*

# Establecer permisos
sudo chmod 660 /var/lib/mysql/mysql-bin.*

# Configurar purga automática en my.cnf
[mysqld]
expire_logs_days = 7
max_binlog_size = 100M

Deshabilitar LOCAL INFILE

Prevenir carga de datos desde archivos locales:

[mysqld]
local-infile=0

Verificar:

SHOW VARIABLES LIKE 'local_infile';

Configuración de Firewall

UFW (Ubuntu/Debian)

# Permitir MySQL desde IP específica
sudo ufw allow from 192.168.1.50 to any port 3306

# Permitir desde subred específica
sudo ufw allow from 192.168.1.0/24 to any port 3306

# Permitir desde múltiples IPs
sudo ufw allow from 192.168.1.50 to any port 3306
sudo ufw allow from 192.168.1.51 to any port 3306

# Eliminar regla si es necesario
sudo ufw status numbered
sudo ufw delete [número_regla]

# Habilitar firewall
sudo ufw enable

# Verificar estado
sudo ufw status verbose

firewalld (CentOS/Rocky Linux)

# Agregar regla rica para IP específica
sudo firewall-cmd --permanent --add-rich-rule='
rule family="ipv4"
source address="192.168.1.50"
port protocol="tcp" port="3306" accept'

# Agregar regla rica para subred
sudo firewall-cmd --permanent --add-rich-rule='
rule family="ipv4"
source address="192.168.1.0/24"
port protocol="tcp" port="3306" accept'

# Eliminar servicio MySQL predeterminado
sudo firewall-cmd --permanent --remove-service=mysql

# Recargar firewall
sudo firewall-cmd --reload

# Verificar reglas
sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules

iptables

# Permitir desde IP específica
sudo iptables -A INPUT -p tcp -s 192.168.1.50 --dport 3306 -j ACCEPT

# Permitir desde subred
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 3306 -j ACCEPT

# Descartar todo otro tráfico MySQL
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP

# Guardar reglas (Ubuntu/Debian)
sudo netfilter-persistent save

# Guardar reglas (CentOS/Rocky)
sudo service iptables save

# Ver reglas
sudo iptables -L -n -v

Registro y Monitoreo de Seguridad

Habilitar General Query Log

Registrar todas las consultas para auditoría de seguridad:

[mysqld]
general_log = 1
general_log_file = /var/log/mysql/general.log

Ver log:

sudo tail -f /var/log/mysql/general.log

Advertencia: El general log puede crecer mucho. Usar solo para solución de problemas o auditoría de corto plazo.

Registro de Errores

Configurar registro completo de errores:

[mysqld]
log_error = /var/log/mysql/error.log
log_error_verbosity = 3  # 1=errores, 2=+advertencias, 3=+notas

Binary Logging para Auditoría

Habilitar binary logging:

[mysqld]
log_bin = /var/log/mysql/mysql-bin
binlog_format = ROW
expire_logs_days = 7
max_binlog_size = 100M
sync_binlog = 1  # Sincronizar a disco para confiabilidad

Plugin de Auditoría (MySQL Enterprise / MariaDB)

Plugin de Auditoría de MariaDB:

# Instalar plugin de auditoría
sudo mysql -u root -p <<EOF
INSTALL SONAME 'server_audit';
EOF

Configurar en my.cnf:

[mariadb]
plugin_load_add = server_audit
server_audit_logging = ON
server_audit_events = CONNECT,QUERY,TABLE
server_audit_output_type = FILE
server_audit_file_path = /var/log/mysql/audit.log
server_audit_file_rotate_size = 1000000
server_audit_file_rotations = 9

Ver configuración de auditoría:

SHOW VARIABLES LIKE 'server_audit%';

Monitorear Intentos de Inicio de Sesión Fallidos

Verificar fallos de autenticación:

# Verificar log de errores para fallos de autenticación
sudo grep "Access denied" /var/log/mysql/error.log

# Contar intentos fallidos por usuario
sudo grep "Access denied for user" /var/log/mysql/error.log | awk '{print $NF}' | sort | uniq -c | sort -rn

# Verificar intentos fallidos recientes
sudo tail -100 /var/log/mysql/error.log | grep "Access denied"

Mejores Prácticas de Seguridad

Eliminar Usuarios Anónimos

-- Verificar usuarios anónimos
SELECT User, Host FROM mysql.user WHERE User = '';

-- Eliminar usuarios anónimos
DELETE FROM mysql.user WHERE User = '';
FLUSH PRIVILEGES;

Eliminar Base de Datos Test

-- Eliminar base de datos test
DROP DATABASE IF EXISTS test;

-- Eliminar privilegios de base de datos test
DELETE FROM mysql.db WHERE Db LIKE 'test%';
FLUSH PRIVILEGES;

Deshabilitar Enlaces Simbólicos

Prevenir problemas de seguridad con enlaces simbólicos:

[mysqld]
symbolic-links=0

Asegurar Procedimientos Almacenados

Limitar creación de procedimientos almacenados:

-- Revocar CREATE ROUTINE de usuarios regulares
REVOKE CREATE ROUTINE ON *.* FROM 'app_user'@'localhost';

-- Solo otorgar a usuarios específicos que lo necesiten
GRANT CREATE ROUTINE ON myapp_db.* TO 'developer'@'localhost';

Deshabilitar SHOW DATABASES

Prevenir que usuarios vean lista de bases de datos:

-- Eliminar privilegio SHOW DATABASES
REVOKE SHOW DATABASES ON *.* FROM 'app_user'@'localhost';

-- El usuario aún puede acceder a bases de datos otorgadas
GRANT ALL ON specific_db.* TO 'app_user'@'localhost';

Restricciones de Privilegio FILE

Eliminar privilegio FILE de usuarios:

-- Verificar usuarios con privilegio FILE
SELECT User, Host FROM mysql.user WHERE File_priv = 'Y';

-- Revocar privilegio FILE
REVOKE FILE ON *.* FROM 'user'@'host';

-- Solo otorgar a usuarios de backup si es absolutamente necesario
GRANT FILE ON *.* TO 'backup_user'@'localhost';

Limitar Recursos de Base de Datos

Prevenir agotamiento de recursos:

-- Crear usuario con límites de recursos
CREATE USER 'limited_user'@'localhost' IDENTIFIED BY 'password'
WITH MAX_QUERIES_PER_HOUR 1000
     MAX_UPDATES_PER_HOUR 500
     MAX_CONNECTIONS_PER_HOUR 100
     MAX_USER_CONNECTIONS 5;

-- Modificar usuario existente
ALTER USER 'app_user'@'localhost'
WITH MAX_QUERIES_PER_HOUR 10000
     MAX_CONNECTIONS_PER_HOUR 500
     MAX_USER_CONNECTIONS 10;

-- Ver límites de recursos
SELECT User, Host, max_questions, max_updates, max_connections, max_user_connections
FROM mysql.user
WHERE User = 'limited_user';

Auditoría de Seguridad

Auditorías de Seguridad Regulares

Crear script de auditoría:

sudo nano /usr/local/bin/mysql-security-audit.sh

Agregar contenido:

#!/bin/bash

REPORT_FILE="/var/log/mysql/security_audit_$(date +%Y%m%d).txt"

echo "Auditoría de Seguridad MySQL - $(date)" > $REPORT_FILE
echo "======================================" >> $REPORT_FILE

# Verificar usuarios sin contraseñas
echo -e "\n[CRÍTICO] Usuarios sin contraseñas:" >> $REPORT_FILE
mysql -u root -p[password] -e "SELECT User, Host FROM mysql.user WHERE authentication_string = '' OR authentication_string IS NULL;" >> $REPORT_FILE

# Verificar usuarios con host comodín
echo -e "\n[ADVERTENCIA] Usuarios con host comodín (%):" >> $REPORT_FILE
mysql -u root -p[password] -e "SELECT User, Host FROM mysql.user WHERE Host = '%';" >> $REPORT_FILE

# Verificar usuarios anónimos
echo -e "\n[CRÍTICO] Usuarios anónimos:" >> $REPORT_FILE
mysql -u root -p[password] -e "SELECT User, Host FROM mysql.user WHERE User = '';" >> $REPORT_FILE

# Verificar usuarios con privilegio FILE
echo -e "\n[ADVERTENCIA] Usuarios con privilegio FILE:" >> $REPORT_FILE
mysql -u root -p[password] -e "SELECT User, Host FROM mysql.user WHERE File_priv = 'Y';" >> $REPORT_FILE

# Verificar usuarios con privilegio SUPER
echo -e "\n[INFO] Usuarios con privilegio SUPER:" >> $REPORT_FILE
mysql -u root -p[password] -e "SELECT User, Host FROM mysql.user WHERE Super_priv = 'Y';" >> $REPORT_FILE

# Verificar configuración SSL
echo -e "\n[INFO] Estado SSL:" >> $REPORT_FILE
mysql -u root -p[password] -e "SHOW VARIABLES LIKE '%ssl%';" >> $REPORT_FILE

# Verificar base de datos test
echo -e "\n[ADVERTENCIA] Base de datos test existe:" >> $REPORT_FILE
mysql -u root -p[password] -e "SHOW DATABASES LIKE 'test';" >> $REPORT_FILE

# Verificar plugin de autenticación
echo -e "\n[INFO] Plugins de autenticación en uso:" >> $REPORT_FILE
mysql -u root -p[password] -e "SELECT User, Host, plugin FROM mysql.user;" >> $REPORT_FILE

echo -e "\nAuditoría completada. Reporte guardado en: $REPORT_FILE"

Pruebas de Penetración

Usar mysql-audit para evaluación de seguridad:

# Instalar mysql-audit
git clone https://github.com/habitissimo/mysql-audit.git
cd mysql-audit

# Ejecutar auditoría
./mysql-audit -u root -p password -h localhost

# Revisar recomendaciones

Respuesta a Incidentes

Detectar Actividad Sospechosa

-- Verificar conexiones actuales
SHOW PROCESSLIST;

-- Ver historial de conexión de usuario
SELECT User, Host, TIME, STATE, INFO
FROM information_schema.PROCESSLIST;

-- Verificar intentos de inicio de sesión fallidos (desde log de errores)
sudo grep "Access denied" /var/log/mysql/error.log | tail -20

Responder a Compromiso

Si se sospecha compromiso de base de datos:

-- Bloquear inmediatamente cuentas sospechosas
ALTER USER 'suspicious_user'@'%' ACCOUNT LOCK;

-- Cambiar todas las contraseñas
ALTER USER 'app_user'@'localhost' IDENTIFIED BY 'new_strong_password';

-- Revisar y revocar privilegios excesivos
REVOKE ALL PRIVILEGES ON *.* FROM 'user'@'host';
GRANT SELECT, INSERT, UPDATE ON specific_db.* TO 'user'@'host';

-- Matar conexiones sospechosas
KILL CONNECTION process_id;

-- Revisar permisos para todos los usuarios
SELECT User, Host FROM mysql.user;
SHOW GRANTS FOR 'user'@'host';

Cumplimiento y Estándares

Cumplimiento PCI-DSS

Para datos de tarjetas de pago:

  • Usar criptografía fuerte (TLS 1.2+)
  • Implementar control de acceso fuerte
  • Auditorías de seguridad regulares
  • Registrar y monitorear todo acceso
  • Cambiar contraseñas predeterminadas
  • Deshabilitar servicios innecesarios

Cumplimiento GDPR

Para datos personales:

  • Implementar cifrado en reposo y en tránsito
  • Registro y auditoría de acceso
  • Capacidades de anonimización de datos
  • Procedimientos de derecho de eliminación
  • Procedimientos de notificación de violación de datos

Conclusión

La seguridad de bases de datos es un proceso continuo que requiere vigilancia, auditorías regulares y mantenerse actualizado con mejores prácticas de seguridad y parches. Esta guía ha proporcionado medidas completas de hardening de seguridad para instalaciones de MySQL y MariaDB.

Lista de Verificación de Seguridad

  • ✓ Ejecutar mysql_secure_installation
  • ✓ Implementar políticas de contraseñas fuertes
  • ✓ Usar principio de privilegio mínimo para todos los usuarios
  • ✓ Habilitar cifrado SSL/TLS
  • ✓ Configurar reglas de firewall
  • ✓ Deshabilitar acceso root remoto
  • ✓ Eliminar usuarios anónimos y base de datos test
  • ✓ Habilitar registro y monitoreo de seguridad
  • ✓ Auditorías de seguridad regulares
  • ✓ Mantener MySQL/MariaDB actualizado
  • ✓ Asegurar permisos del sistema de archivos
  • ✓ Documentar procedimientos de seguridad

Resumen de Mejores Prácticas

  1. Nunca usar root para aplicaciones - Crear usuarios dedicados
  2. Siempre usar SSL/TLS para conexiones de red
  3. Implementar defensa en profundidad - Múltiples capas de seguridad
  4. Monitorear continuamente - Revisión regular de logs y alertas
  5. Auditar regularmente - Evaluaciones de seguridad trimestrales
  6. Mantener actualizado - Aplicar parches de seguridad rápidamente
  7. Documentar todo - Procedimientos y cambios de seguridad
  8. Probar backups - Verificar procedimientos de restauración
  9. Limitar acceso - Tanto basado en red como en privilegios
  10. Educar usuarios - Capacitación en concientización de seguridad

Recursos Adicionales

La seguridad no es una tarea de una sola vez sino un compromiso continuo. Revisa y actualiza regularmente tu postura de seguridad para proteger contra amenazas en evolución.