mysql-security-audit.sh
Outil d'audit de sécurité rapide pour MySQL/MariaDB avec rapport colorisé PASS/FAIL.
Informations
| Propriété | Valeur |
|---|---|
| Langage | Bash |
| Catégorie | Base de données / Sécurité |
| Niveau | Intermédiaire |
| Dépendances | mysql client |
Description
Ce script effectue un audit de sécurité rapide d'une instance MySQL ou MariaDB en vérifiant les failles de configuration les plus courantes. Il produit un rapport colorisé indiquant clairement les tests succeededs (PASS) et faileds (FAIL).
Vérifications effectuées :
- Mots de passe vides : Utilisateurs sans authentification
- Root accessible Ă distance :
root@'%'est un risque critique - Utilisateurs anonymes : Comptes sans nom (legacy)
- PrivilĂšges excessifs : Utilisateurs avec
GRANT ALL PRIVILEGES - Bases de données de test : Présence de
testdatabase - Plugin d'authentification : Vérification des méthodes sécurisées
Prérequis
# Client MySQL installé
mysql --version
# AccĂšs administrateur Ă l'instance MySQL
# L'utilisateur doit pouvoir lire mysql.user et information_schema
Cas d'Usage
- Audit de sécurité périodique : Vérification automatisée des configurations de sécurité MySQL avec rapport détaillé
- Conformité réglementaire : Documentation et validation des paramÚtres de sécurité pour audits
- Durcissement post-installation : Identification rapide des failles de sécurité aprÚs installation MySQL
- Validation avant production : Test de sécurité complet avant mise en production d'un serveur de base de données
Script
#!/bin/bash
#===============================================================================
# Script Name: mysql-security-audit.sh
# Description: Rapid security assessment of MySQL/MariaDB instance
# Author: ShellBook
# Date: 2024-01-15
# Version: 1.0
#===============================================================================
set -euo pipefail
IFS=$'\n\t'
# Variables
readonly SCRIPT_NAME=$(basename "$0")
# Colors
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m'
readonly BOLD='\033[1m'
# Default connection parameters
DB_HOST="${MYSQL_HOST:-localhost}"
DB_PORT="${MYSQL_TCP_PORT:-3306}"
DB_USER="${MYSQL_USER:-root}"
DB_SOCKET=""
# Counters
PASS_COUNT=0
FAIL_COUNT=0
WARN_COUNT=0
# Functions
log_pass() {
echo -e " ${GREEN}[PASS]${NC} $1"
((PASS_COUNT++))
}
log_fail() {
echo -e " ${RED}[FAIL]${NC} $1"
((FAIL_COUNT++))
}
log_warn() {
echo -e " ${YELLOW}[WARN]${NC} $1"
((WARN_COUNT++))
}
log_info() {
echo -e " ${BLUE}[INFO]${NC} $1"
}
usage() {
cat << EOF
${CYAN}Usage:${NC} $SCRIPT_NAME [OPTIONS]
Perform a rapid security audit of a MySQL/MariaDB instance.
${CYAN}Options:${NC}
-h, --help Show this help
-H, --host HOST HĂŽte MySQL (default: localhost)
-P, --port PORT Port MySQL (default: 3306)
-u, --user USER Utilisateur MySQL (default: root)
-p, --password Demander le mot de passe
-S, --socket PATH Chemin du socket Unix
${CYAN}Variables d'environnement:${NC}
MYSQL_HOST, MYSQL_TCP_PORT, MYSQL_USER, MYSQL_PWD
${CYAN}Examples:${NC}
$SCRIPT_NAME
$SCRIPT_NAME -H db.example.com -u admin -p
$SCRIPT_NAME -S /var/run/mysqld/mysqld.sock
${CYAN}Vérifications effectuées:${NC}
1. Utilisateurs avec mots de passe vides
2. Root accessible depuis '%' (remote)
3. Existence d'utilisateurs anonymes
4. Utilisateurs avec GRANT ALL PRIVILEGES
5. Présence de la base 'test'
6. Plugins d'authentification non sécurisés
EOF
}
# Build MySQL command with connection parameters
mysql_cmd() {
local cmd="mysql -N -B"
if [[ -n "$DB_SOCKET" ]]; then
cmd="$cmd -S $DB_SOCKET"
else
cmd="$cmd -h $DB_HOST -P $DB_PORT"
fi
cmd="$cmd -u $DB_USER"
echo "$cmd"
}
# Test database connection
test_connection() {
echo -e "\n${BOLD}${CYAN}=== TEST DE CONNEXION ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
if ! $cmd -e "SELECT 1" &> /dev/null; then
echo -e "${RED}Impossible de se connecter Ă MySQL${NC}"
echo "Vérifiez les paramÚtres de connexion et le mot de passe (MYSQL_PWD)"
exit 1
fi
# Get server version
local version
version=$($cmd -e "SELECT VERSION()")
log_info "Connecté à MySQL/MariaDB: $version"
# Check if MariaDB or MySQL
if [[ "$version" == *"MariaDB"* ]]; then
log_info "Type: MariaDB"
else
log_info "Type: MySQL"
fi
}
# Check 1: Users with empty passwords
check_empty_passwords() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 1: Mots de passe vides ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
# Query differs between MySQL 5.7+/8.0 and older versions
local query="
SELECT User, Host
FROM mysql.user
WHERE (authentication_string = '' OR authentication_string IS NULL)
AND plugin NOT IN ('auth_socket', 'unix_socket', 'auth_pam')
AND User != ''
"
local result
result=$($cmd -e "$query" 2>/dev/null || echo "QUERY_ERROR")
if [[ "$result" == "QUERY_ERROR" ]]; then
# Fallback for older MySQL versions
query="
SELECT User, Host
FROM mysql.user
WHERE (Password = '' OR Password IS NULL)
AND User != ''
"
result=$($cmd -e "$query" 2>/dev/null || echo "")
fi
if [[ -z "$result" ]]; then
log_pass "Aucun utilisateur avec mot de passe vide"
else
log_fail "Utilisateurs avec mot de passe vide détectés:"
echo "$result" | while read -r user host; do
echo -e " ${RED}â '$user'@'$host'${NC}"
done
echo -e "\n ${YELLOW}Recommandation: ALTER USER 'user'@'host' IDENTIFIED BY 'strong_password';${NC}"
fi
}
# Check 2: Root accessible from remote hosts
check_remote_root() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 2: Root accessible Ă distance ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
local query="
SELECT User, Host
FROM mysql.user
WHERE User = 'root'
AND Host NOT IN ('localhost', '127.0.0.1', '::1')
"
local result
result=$($cmd -e "$query" 2>/dev/null)
if [[ -z "$result" ]]; then
log_pass "Root n'est pas accessible Ă distance"
else
log_fail "Root est accessible depuis des hĂŽtes distants:"
echo "$result" | while read -r user host; do
echo -e " ${RED}â '$user'@'$host'${NC}"
done
# Special warning for root@'%'
if echo "$result" | grep -q "%"; then
echo -e "\n ${RED}â ïž CRITIQUE: root@'%' permet l'accĂšs depuis n'importe quelle IP!${NC}"
fi
echo -e "\n ${YELLOW}Recommandation: DROP USER 'root'@'%'; ou restreindre à une IP spécifique${NC}"
fi
}
# Check 3: Anonymous users
check_anonymous_users() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 3: Utilisateurs anonymes ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
local query="
SELECT User, Host
FROM mysql.user
WHERE User = ''
"
local result
result=$($cmd -e "$query" 2>/dev/null)
if [[ -z "$result" ]]; then
log_pass "Aucun utilisateur anonyme"
else
log_fail "Utilisateurs anonymes détectés:"
echo "$result" | while read -r user host; do
echo -e " ${RED}â ''@'$host'${NC}"
done
echo -e "\n ${YELLOW}Recommandation: DROP USER ''@'localhost'; DROP USER ''@'hostname';${NC}"
fi
}
# Check 4: Users with GRANT ALL PRIVILEGES
check_grant_all() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 4: Utilisateurs avec GRANT ALL ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
# Check for users with all privileges on *.*
local query="
SELECT DISTINCT User, Host
FROM mysql.user
WHERE (
Select_priv = 'Y' AND Insert_priv = 'Y' AND Update_priv = 'Y'
AND Delete_priv = 'Y' AND Create_priv = 'Y' AND Drop_priv = 'Y'
AND Reload_priv = 'Y' AND Shutdown_priv = 'Y' AND Process_priv = 'Y'
AND File_priv = 'Y' AND Grant_priv = 'Y' AND References_priv = 'Y'
AND Index_priv = 'Y' AND Alter_priv = 'Y' AND Super_priv = 'Y'
)
AND User NOT IN ('root', 'mysql.sys', 'mysql.session', 'mysql.infoschema', 'mariadb.sys')
"
local result
result=$($cmd -e "$query" 2>/dev/null)
if [[ -z "$result" ]]; then
log_pass "Aucun utilisateur non-root avec tous les privilĂšges"
else
log_warn "Utilisateurs avec GRANT ALL PRIVILEGES (hors root):"
echo "$result" | while read -r user host; do
echo -e " ${YELLOW}â '$user'@'$host'${NC}"
done
echo -e "\n ${YELLOW}Recommandation: Appliquer le principe du moindre privilĂšge${NC}"
echo -e " ${YELLOW}REVOKE ALL PRIVILEGES ON *.* FROM 'user'@'host';${NC}"
echo -e " ${YELLOW}GRANT SELECT, INSERT, UPDATE ON mydb.* TO 'user'@'host';${NC}"
fi
}
# Check 5: Test database exists
check_test_database() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 5: Base de donnĂ©es 'test' ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
local query="SHOW DATABASES LIKE 'test'"
local result
result=$($cmd -e "$query" 2>/dev/null)
if [[ -z "$result" ]]; then
log_pass "La base de données 'test' does not exist"
else
log_warn "La base de données 'test' existe"
echo -e " ${YELLOW}Cette base est accessible par défaut à tous les utilisateurs${NC}"
echo -e "\n ${YELLOW}Recommandation: DROP DATABASE test;${NC}"
fi
}
# Check 6: Authentication plugins
check_auth_plugins() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 6: Plugins d'authentification ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
local query="
SELECT User, Host, plugin
FROM mysql.user
WHERE plugin IN ('mysql_old_password', 'mysql_native_password')
AND User != ''
ORDER BY plugin, User
"
local result
result=$($cmd -e "$query" 2>/dev/null || echo "")
# Check for old password plugin
if echo "$result" | grep -q "mysql_old_password"; then
log_fail "Utilisateurs avec mysql_old_password (obsolÚte et non sécurisé):"
echo "$result" | grep "mysql_old_password" | while read -r user host plugin; do
echo -e " ${RED}â '$user'@'$host' ($plugin)${NC}"
done
fi
# Check for native password (warning for MySQL 8+)
local version
version=$($cmd -e "SELECT @@version" 2>/dev/null)
if [[ "$version" == 8.* ]]; then
local native_count
native_count=$(echo "$result" | grep -c "mysql_native_password" || echo "0")
if [[ "$native_count" -gt 0 ]]; then
log_warn "Utilisateurs avec mysql_native_password (MySQL 8+ recommande caching_sha2_password):"
echo "$result" | grep "mysql_native_password" | head -5 | while read -r user host plugin; do
echo -e " ${YELLOW}â '$user'@'$host'${NC}"
done
if [[ "$native_count" -gt 5 ]]; then
echo -e " ${YELLOW}... et $((native_count - 5)) autres${NC}"
fi
else
log_pass "Tous les utilisateurs utilisent des plugins d'authentification sécurisés"
fi
else
log_pass "Plugin mysql_native_password approprié pour cette version"
fi
}
# Check 7: Validate_password plugin
check_password_policy() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 7: Politique de mots de passe ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
# Check if validate_password is installed
local result
result=$($cmd -e "SHOW VARIABLES LIKE 'validate_password%'" 2>/dev/null || echo "")
if [[ -z "$result" ]]; then
log_warn "Le plugin validate_password n'est pas installé"
echo -e " ${YELLOW}Ce plugin renforce la politique de mots de passe${NC}"
echo -e "\n ${YELLOW}Installation: INSTALL COMPONENT 'file://component_validate_password';${NC}"
else
log_pass "Plugin validate_password actif"
# Show current policy
local policy
policy=$($cmd -e "SHOW VARIABLES LIKE 'validate_password.policy'" 2>/dev/null | awk '{print $2}')
if [[ -n "$policy" ]]; then
log_info "Politique actuelle: $policy"
fi
fi
}
# Check 8: SSL/TLS Configuration
check_ssl_config() {
echo -e "\n${BOLD}${CYAN}=== VĂRIFICATION 8: Configuration SSL/TLS ===${NC}\n"
local cmd
cmd=$(mysql_cmd)
local ssl_status
ssl_status=$($cmd -e "SHOW VARIABLES LIKE 'have_ssl'" 2>/dev/null | awk '{print $2}')
if [[ "$ssl_status" == "YES" ]]; then
log_pass "SSL/TLS est disponible"
# Check if SSL is required for any users
local ssl_required
ssl_required=$($cmd -e "SELECT User, Host, ssl_type FROM mysql.user WHERE ssl_type != ''" 2>/dev/null)
if [[ -n "$ssl_required" ]]; then
log_info "Utilisateurs avec exigence SSL:"
echo "$ssl_required" | while read -r user host ssl_type; do
echo -e " â '$user'@'$host' ($ssl_type)"
done
else
log_warn "Aucun utilisateur n'exige SSL pour la connexion"
echo -e " ${YELLOW}Recommandation: ALTER USER 'user'@'host' REQUIRE SSL;${NC}"
fi
else
log_warn "SSL/TLS n'est pas activé"
echo -e " ${YELLOW}Les connexions ne sont pas chiffrées${NC}"
fi
}
# Print summary
print_summary() {
echo -e "\n${BOLD}${CYAN}================================================================${NC}"
echo -e "${BOLD}${CYAN} RĂSUMĂ DE L'AUDIT${NC}"
echo -e "${BOLD}${CYAN}================================================================${NC}\n"
echo -e " ${GREEN}PASS:${NC} $PASS_COUNT"
echo -e " ${RED}FAIL:${NC} $FAIL_COUNT"
echo -e " ${YELLOW}WARN:${NC} $WARN_COUNT"
echo -e "\n${BOLD}Score de sécurité:${NC}"
local total=$((PASS_COUNT + FAIL_COUNT + WARN_COUNT))
if [[ $total -gt 0 ]]; then
local score=$((PASS_COUNT * 100 / total))
if [[ $FAIL_COUNT -eq 0 && $WARN_COUNT -eq 0 ]]; then
echo -e " ${GREEN}ââââââââââ 100% - Excellent!${NC}"
elif [[ $FAIL_COUNT -eq 0 ]]; then
echo -e " ${YELLOW}ââââââââââ ${score}% - Bon, quelques amĂ©liorations possibles${NC}"
elif [[ $FAIL_COUNT -le 2 ]]; then
echo -e " ${YELLOW}ââââââââââ ${score}% - Attention requise${NC}"
else
echo -e " ${RED}ââââââââââ ${score}% - Actions urgentes nĂ©cessaires!${NC}"
fi
fi
if [[ $FAIL_COUNT -gt 0 ]]; then
echo -e "\n${RED}â ïž Des problĂšmes de sĂ©curitĂ© critiques ont Ă©tĂ© dĂ©tectĂ©s.${NC}"
echo -e "${RED} Veuillez les corriger dĂšs que possible.${NC}"
fi
echo -e "\n${BOLD}${CYAN}================================================================${NC}\n"
}
# Parse arguments
main() {
local ask_password=false
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-H|--host)
DB_HOST="$2"
shift 2
;;
-P|--port)
DB_PORT="$2"
shift 2
;;
-u|--user)
DB_USER="$2"
shift 2
;;
-p|--password)
ask_password=true
shift
;;
-S|--socket)
DB_SOCKET="$2"
shift 2
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
usage
exit 1
;;
esac
done
# Ask for password if requested
if [[ "$ask_password" == "true" ]]; then
echo -n "Mot de passe MySQL: "
read -rs MYSQL_PWD
export MYSQL_PWD
echo ""
fi
# Header
echo -e "\n${BOLD}${CYAN}================================================================${NC}"
echo -e "${BOLD}${CYAN} AUDIT DE SĂCURITĂ MySQL/MariaDB${NC}"
echo -e "${BOLD}${CYAN}================================================================${NC}"
echo -e "HĂŽte: ${BOLD}$DB_HOST:$DB_PORT${NC} | Utilisateur: ${BOLD}$DB_USER${NC}"
# Run checks
test_connection
check_empty_passwords
check_remote_root
check_anonymous_users
check_grant_all
check_test_database
check_auth_plugins
check_password_policy
check_ssl_config
# Print summary
print_summary
# Exit code based on failures
if [[ $FAIL_COUNT -gt 0 ]]; then
exit 1
fi
exit 0
}
# Execute
main "$@"
Usage
Audit Basique
# Audit local avec root
./mysql-security-audit.sh
# Avec demande de mot de passe
./mysql-security-audit.sh -p
# Via socket Unix
./mysql-security-audit.sh -S /var/run/mysqld/mysqld.sock
Audit Distant
# Instance distante
./mysql-security-audit.sh -H db.example.com -u admin -p
# Port personnalisé
./mysql-security-audit.sh -H db.example.com -P 3307 -u admin -p
# Avec variable d'environnement
MYSQL_PWD=secret ./mysql-security-audit.sh -H db.example.com -u admin
Exemple de Sortie
================================================================
AUDIT DE SĂCURITĂ MySQL/MariaDB
================================================================
HĂŽte: localhost:3306 | Utilisateur: root
=== TEST DE CONNEXION ===
[INFO] Connecté à MySQL/MariaDB: 8.0.35
[INFO] Type: MySQL
=== VĂRIFICATION 1: Mots de passe vides ===
[PASS] Aucun utilisateur avec mot de passe vide
=== VĂRIFICATION 2: Root accessible Ă distance ===
[FAIL] Root est accessible depuis des hĂŽtes distants:
â 'root'@'%'
â ïž CRITIQUE: root@'%' permet l'accĂšs depuis n'importe quelle IP!
Recommandation: DROP USER 'root'@'%'; ou restreindre à une IP spécifique
=== VĂRIFICATION 3: Utilisateurs anonymes ===
[PASS] Aucun utilisateur anonyme
=== VĂRIFICATION 4: Utilisateurs avec GRANT ALL ===
[WARN] Utilisateurs avec GRANT ALL PRIVILEGES (hors root):
â 'backup_user'@'localhost'
Recommandation: Appliquer le principe du moindre privilĂšge
=== VĂRIFICATION 5: Base de donnĂ©es 'test' ===
[WARN] La base de données 'test' existe
Cette base est accessible par défaut à tous les utilisateurs
Recommandation: DROP DATABASE test;
=== VĂRIFICATION 6: Plugins d'authentification ===
[PASS] Tous les utilisateurs utilisent des plugins d'authentification sécurisés
=== VĂRIFICATION 7: Politique de mots de passe ===
[PASS] Plugin validate_password actif
[INFO] Politique actuelle: MEDIUM
=== VĂRIFICATION 8: Configuration SSL/TLS ===
[PASS] SSL/TLS est disponible
[WARN] Aucun utilisateur n'exige SSL pour la connexion
Recommandation: ALTER USER 'user'@'host' REQUIRE SSL;
================================================================
RĂSUMĂ DE L'AUDIT
================================================================
PASS: 5
FAIL: 1
WARN: 3
Score de sécurité:
ââââââââââ 55% - Attention requise
â ïž Des problĂšmes de sĂ©curitĂ© critiques ont Ă©tĂ© dĂ©tectĂ©s.
Veuillez les corriger dĂšs que possible.
================================================================
Vérifications Effectuées
| # | Vérification | Sévérité | Description |
|---|---|---|---|
| 1 | Mots de passe vides | CRITIQUE | Utilisateurs sans authentification |
| 2 | Root Ă distance | CRITIQUE | root@'%' accessible depuis tout IP |
| 3 | Utilisateurs anonymes | HAUTE | Comptes sans nom (legacy) |
| 4 | GRANT ALL | MOYENNE | PrivilĂšges excessifs |
| 5 | Base test | BASSE | Base accessible par tous |
| 6 | Auth plugins | MOYENNE | Méthodes obsolÚtes |
| 7 | Password policy | MOYENNE | Politique de complexité |
| 8 | SSL/TLS | HAUTE | Chiffrement des connexions |
Options
| Option | Description |
|---|---|
-h, --help |
Affiche l'aide |
-H, --host HOST |
HĂŽte MySQL (default: localhost) |
-P, --port PORT |
Port MySQL (default: 3306) |
-u, --user USER |
Utilisateur MySQL (default: root) |
-p, --password |
Demander le mot de passe interactivement |
-S, --socket PATH |
Chemin du socket Unix |
Actions Immédiates
Si l'audit révÚle des FAIL, corrigez immédiatement :
Hardening MySQL
AprÚs l'audit, exécutez également :
Ce script interactif configure :
- Mot de passe root
- Suppression utilisateurs anonymes
- Désactivation accÚs root distant
- Suppression base test
Voir Aussi
- check-mysql.sh - Vérification santé MySQL
- pg-bloat-check.sh - Analyse bloat PostgreSQL
- redis_key_auditor.py - Audit clés Redis