MySQL : sécurisation des accès et des données
Quand on pense à sécuriser un site web, on se concentre souvent sur les mots de passe, le code applicatif ou les pare-feux réseau. Mais la base de données est parfois la grande oubliée des réflexes de protection. Dans MySQL, une simple mauvaise configuration peut suffire à exposer des informations sensibles, perturber un fonctionnement, voire permettre une intrusion durable. Et pourtant, sécuriser une base n’exige pas d’être expert en cybersécurité : ce sont souvent des gestes simples et des choix lucides qui font la différence. Dans cet article, nous allons parcourir les bons réflexes à adopter, que la base soit utilisée en local pour un projet personnel ou en production dans un outil en ligne.
Ce texte prolonge les démarches abordées dans Démarrer avec PHPMyAdmin : création de votre première base de données, et prépare le terrain pour mieux comprendre pourquoi les requêtes préparées sont une étape indispensable.
Comprendre ce que l’on protège
Avant de sécuriser quoi que ce soit, il faut savoir ce que contient une base. Même modeste, une base MySQL peut stocker des identifiants d’accès, des informations personnelles, des contenus sensibles, des historiques de navigation ou des données administratives. Oublier que tout cela transite ou repose dans des tables, c’est oublier qu’on a quelque chose à perdre.
Même en local, une base mal configurée peut être consultée par erreur, modifiée involontairement, ou aspirée en quelques secondes via un simple export. Ce que l’on protège ici, ce ne sont pas seulement des lignes : ce sont des usages, des engagements, parfois même des personnes. Imaginez une application interne de réservation : une simple table utilisateurs
peut contenir des noms, des e-mails, des mots de passe, des dates de présence, des commentaires. Laisser cette base ouverte, c’est laisser toute l’activité lisible par n’importe qui, voire modifiable par accident.

Les accès utilisateurs : un principe de précaution
Dans MySQL, tout commence par l’utilisateur qui se connecte. Par défaut, on travaille souvent avec le compte root
, surpuissant, sans limite. C’est pratique, mais c’est une erreur dès qu’on quitte le cadre du test local. Chaque projet devrait avoir son propre utilisateur, avec des droits limités au strict nécessaire. C’est ce que font automatiquement les outils comme Plesk, cPanel ou phpMyAdmin lorsqu’on crée une base via l’interface : un identifiant est généré, associé à une base précise, et seul ce compte y aura accès.
Derrière ces interfaces, tout repose sur des commandes SQL classiques. On peut donc créer manuellement un utilisateur avec CREATE USER
, puis lui donner accès à une base avec GRANT
. Toutes ces informations sont stockées dans les tables internes de MySQL, notamment mysql.user
et mysql.db
, qui décrivent qui peut faire quoi, et depuis où. Par défaut, ces tables ne sont pas modifiables directement, mais chaque commande SQL vient y inscrire les règles de manière déclarative.
Ce que l’on précise après le @
dans CREATE USER
ne désigne pas où se trouve la base, mais bien d’où l’utilisateur est autorisé à se connecter. En voici quelques cas concrets :
-- Création d’un utilisateur local uniquement
CREATE USER 'siteweb'@'localhost' IDENTIFIED BY 'motDePasseSécurisé123';
-- (optionnel) Pour un serveur distant spécifique
CREATE USER 'siteweb'@'192.0.2.15' IDENTIFIED BY 'motDePasseSécurisé123';
-- Attribution des droits sur une base précise
GRANT SELECT, INSERT, UPDATE ON ma_base.* TO 'siteweb'@'localhost';
-- Application immédiate des changements
FLUSH PRIVILEGES;
Dans cet exemple, l’utilisateur siteweb
est autorisé à accéder à la base ma_base
depuis le même serveur (localhost
), mais pas depuis l’extérieur. Le mot de passe est stocké de façon chiffrée dans la table mysql.user
, et les privilèges sont enregistrés dans mysql.db
. On peut ensuite visualiser ce qui a été accordé avec :
SHOW GRANTS FOR 'siteweb'@'localhost';
C’est la combinaison entre le nom d’utilisateur et son origine (Host
) qui définit un accès. Un même identifiant peut donc exister plusieurs fois avec des autorisations différentes, selon qu’il vienne de localhost
, d’une IP fixe ou de n’importe où (%
).
Définir les bons privilèges : ni trop, ni pas assez
Une fois l’utilisateur créé, il faut lui accorder des droits précis, ni plus, ni moins. MySQL permet de définir très finement ce qu’un compte peut faire : lire (SELECT
), ajouter (INSERT
), modifier (UPDATE
), supprimer (DELETE
), créer des tables (CREATE
), ou encore administrer les privilèges (GRANT OPTION
). Trop de droits, c’est trop de risques ; pas assez, c’est une application qui ne fonctionne pas. On ne donne pas DROP
à un site qui ne crée rien, ni ALL PRIVILEGES
par habitude. L’idée n’est pas de bloquer, mais de limiter la surface d’erreur. Un site vitrine n’a souvent besoin que de lire des données, une interface de saisie peut ajouter ou modifier, mais ne jamais supprimer. Plus les droits sont serrés, plus les dégâts sont contenus si quelque chose dérape.
Dans un environnement de production, des outils comme Plesk, cPanel ou phpMyAdmin permettent généralement de gérer ces permissions via des interfaces visuelles.

Mais derrière ces réglages se trouvent toujours les mêmes commandes SQL, accessibles à la main via le module SQL de phpMyAdmin, ou depuis un script. Voici quelques exemples concrets, chacun adapté à un usage courant :
-- Lecture seule : consultation d’un site vitrine
GRANT SELECT ON ma_base.* TO 'lecteur'@'localhost';
-- Saisie simple : formulaire de contact ou d’inscription
GRANT SELECT, INSERT ON ma_base.* TO 'saisie'@'localhost';
-- Interface avec modification : espace personnel utilisateur
GRANT SELECT, INSERT, UPDATE ON ma_base.* TO 'editor'@'localhost';
-- Suppression autorisée : usage sensible, à réserver aux administrateurs
GRANT SELECT, INSERT, UPDATE, DELETE ON ma_base.* TO 'gestion'@'localhost';
-- À éviter : droits complets sans justification
GRANT ALL PRIVILEGES ON ma_base.* TO 'troppuissant'@'localhost';
Dans chaque cas, on construit un accès adapté à la tâche. Cela peut sembler contraignant, mais c’est précisément ce découpage qui permet de rester maître de ce que fait réellement chaque partie du système.
Connexions locales vs distantes : savoir fermer les portes
Un compte MySQL n’est pas seulement lié à un nom d’utilisateur, mais aussi à l’origine de la connexion autorisée. Une erreur fréquente consiste à accorder l’accès depuis n’importe où ('%'
), même quand la base et l’application se trouvent sur le même serveur. Ce type d’ouverture n’est quasiment jamais nécessaire en production classique, et augmente inutilement la surface d’attaque.
Il est donc recommandé de limiter chaque utilisateur à une adresse précise, comme 'localhost'
pour un usage strictement local, ou à une IP fixe pour un frontal distant. Cette restriction peut être définie directement dans SQL, au moment de la création de l’utilisateur avec CREATE USER 'nom'@'hôte'
, ce qui constitue déjà une protection forte. Il est également possible d’avoir plusieurs entrées pour le même nom d’utilisateur, chacune associée à une origine différente, avec des droits distincts.

Pour aller plus loin dans le contrôle réseau (écoute sur le port MySQL, blocage global des connexions distantes…), nous verrons dans le chapitre suivant comment protéger l’environnement serveur lui-même, via des réglages comme bind-address
ou skip-networking
.
Sécuriser les fichiers et l’environnement
Une base bien configurée ne suffit pas si l’environnement qui l’entoure reste exposé. Le fichier my.cnf
, les sauvegardes .sql
, les scripts d’installation ou les exports automatiques peuvent révéler des informations sensibles, comme les mots de passe, les noms des bases ou les réglages réseau. C’est souvent par ces chemins-là qu’un accès est contourné. Il faut donc s’assurer que les fichiers de configuration sont protégés en lecture, que les exports SQL ne traînent pas dans un dossier public (/tmp
, /www
, /documents
), et que les outils d’administration (comme phpMyAdmin) ne soient pas accessibles sans filtrage.
Sur le serveur MySQL lui-même, on peut aller plus loin en restreignant les connexions réseau. Cela se fait dans le fichier my.cnf
, généralement situé dans /etc/mysql/
ou /etc/my.cnf
, en ajoutant ou ajustant ces lignes :
[mysqld]
bind-address = 127.0.0.1
skip-networking
La ligne bind-address
permet de limiter MySQL à écouter uniquement sur le port local (127.0.0.1
), empêchant toute connexion distante. La directive skip-networking
désactive même complètement l’écoute réseau, forçant l’usage du socket Unix local. Ces options sont puissantes, mais ne sont accessibles que si l’on a la main sur le serveur.
Dans un hébergement mutualisé, ces réglages ne sont pas modifiables : seul l’administrateur du serveur y a accès. En revanche, on peut souvent choisir l’adresse d’hôte (localhost
ou non) au moment de créer l’utilisateur SQL, ce qui reste un moyen de filtrage important.
Sur une machine virtuelle ou un serveur dédié, c’est l’utilisateur lui-même qui peut éditer le fichier my.cnf
et redémarrer MySQL. Il faut alors le faire avec précaution : une erreur de syntaxe ou un blocage réseau peut rendre le serveur inaccessible à distance.
Sur un poste de développement local avec XAMPP ou MAMP, ces réglages sont modifiables dans le fichier my.ini
, à condition de redémarrer le service après modification. Dans un environnement web plus encadré comme Plesk ou cPanel, certaines options réseau peuvent être configurées via l’interface, mais le niveau de contrôle varie selon les droits attribués à l’utilisateur. Dans tous les cas, savoir ce que l’on expose et depuis où reste une clé de sécurité.

Protéger les échanges : chiffrer les connexions si besoin
Lorsqu’une base de données est utilisée en local, les échanges passent par le socket interne du système, sans transiter sur le réseau. Mais dès qu’une application se connecte à distance — même depuis un serveur frontal hébergé ailleurs — les requêtes SQL circulent en clair, y compris les identifiants, les contenus, ou les mots de passe hachés. Pour éviter qu’un tiers intercepte ces données en transit, MySQL permet de chiffrer les connexions via SSL/TLS. Cette sécurité est particulièrement utile dans les environnements répartis, les architectures en cloud, ou les projets exposés sur Internet.
Le chiffrement peut être activé côté serveur avec l’option require_secure_transport = ON
, qui impose aux clients d’établir une connexion sécurisée à l’aide d’un certificat valide. Une fois en place, la connexion passe alors par un tunnel chiffré. Le client MySQL peut s’y connecter avec les bons fichiers, par exemple :
mysql \
--ssl-ca=ca.pem \ # certificat de l’autorité de certification (racine)
--ssl-cert=client-cert.pem \ # certificat client signé par l’autorité
--ssl-key=client-key.pem \ # clé privée correspondant au certificat client
-u nom \ # nom d’utilisateur MySQL
-p \ # demande du mot de passe (saisi après validation)
-h mon-serveur # adresse du serveur MySQL distant
Dans un script PHP, il est aussi possible d’activer cette couche de sécurité en passant les certificats au moment de l’ouverture de la connexion :
$pdo = new PDO('mysql:host=mon-serveur;dbname=ma_base', 'utilisateur', 'motdepasse', [
PDO::MYSQL_ATTR_SSL_CA => '/chemin/ca.pem',
PDO::MYSQL_ATTR_SSL_CERT => '/chemin/client-cert.pem',
PDO::MYSQL_ATTR_SSL_KEY => '/chemin/client-key.pem'
]);
Mais cette mise en place reste technique, et pourrait faire l’objet d’un article dédié : il faut gérer les certificats, assurer leur validité, configurer à la fois le client et le serveur, et parfois adapter le code existant. Dans un hébergement mutualisé ou via un panneau comme Plesk ou cPanel, cette option est souvent désactivée ou déjà préconfigurée. Elle n’est pas toujours indispensable, mais dès qu’un accès distant est autorisé, mieux vaut y penser dès le départ.
Protéger les mots de passe avec un hachage irréversible
Une base bien protégée n’a aucun sens si les mots de passe qu’elle contient sont stockés en clair. C’est une faille absolue, même en environnement fermé. Lorsqu’un utilisateur crée un compte, le mot de passe ne doit jamais être conservé tel quel, mais transformé en une empreinte irréversible, qu’on appelle hachage.
Dans MySQL, il est possible de le faire dès l’insertion grâce à la fonction SHA2()
, qui génère une chaîne fixe à partir d’un mot de passe quelconque. On ne peut plus retrouver l’original, mais on peut le comparer lors de la connexion. Ce mécanisme simple permet d’éviter qu’un export de table révèle tous les accès d’un seul coup.
-- Mauvais réflexe : insertion en clair (à proscrire)
INSERT INTO utilisateurs (email, motdepasse)
VALUES ('alice@mail.fr', 'Papillon2025');
-- Meilleure pratique SQL : hachage SHA-256 à l’insertion
INSERT INTO utilisateurs (email, motdepasse)
VALUES ('bob@mail.fr', SHA2('MonMotDePasse123', 256));
-- Vérification à la connexion : on re-hachage la saisie et on compare
SELECT id
FROM utilisateurs
WHERE email = 'bob@mail.fr'
AND motdepasse = SHA2('MotDepasseSaisiParBob', 256);
Il ne remplace pas les méthodes modernes de hachage avec salage utilisées côté applicatif (password_hash()
en PHP), mais constitue déjà un filet de sécurité utile, notamment si la base est remplie via des requêtes SQL ou des scripts d’initialisation. À noter que même si le mot de passe est bien haché, il reste transmis en clair depuis le navigateur vers le serveur : sans connexion HTTPS, il peut être intercepté. Le chiffrement côté transport (SSL/TLS) reste donc indispensable en complément.
<?php
/* Hachage moderne côté PHP — salé et itéré automatiquement */
$hash = password_hash('MonMotDePasse123', PASSWORD_DEFAULT);
/* … plus tard, lors de la connexion … */
$motDePasseSaisi = $_POST['pass'];
$hashDepuisLaBase = $row['motdepasse']; // récupéré par requête préparée
if (password_verify($motDePasseSaisi, $hashDepuisLaBase)) {
// Accès autorisé
} else {
// Accès refusé
}
?>
Dans la table, le champ motdepasse
ne contient plus qu’une empreinte illisible ; en SQL pur elle est fixe (SHA2), tandis qu’en PHP elle intègre un sel et des itérations invisibles mais vérifiables via password_verify()
. Ainsi, même si la table fuit, aucun mot de passe exploitable n’apparaît.
Faut-il hacher toutes les données ?
Non. Le hachage est une opération irréversible : une fois la donnée transformée, elle ne peut plus être relue. Cela convient parfaitement aux mots de passe, que l’on ne consulte jamais, mais uniquement compare à l’entrée utilisateur. En revanche, pour des informations qui doivent être réaffichées — nom, adresse, numéro de téléphone, données de commande — le hachage rendrait l’application inutilisable.
Dans ces cas-là, la protection ne passe pas par l’empreinte, mais par le contrôle des accès, l’usage de champs limités, et parfois un chiffrement réversible (notamment avec AES_ENCRYPT()
dans MySQL). La sécurité ne consiste pas à rendre tout illisible, mais à rendre seules les données nécessaires accessibles, au bon moment, et par la bonne personne.
Pour les données sensibles que l’on doit pouvoir relire — comme un numéro de sécurité sociale, un téléphone privé, ou un RIB — il est préférable de les chiffrer plutôt que de les hacher. MySQL propose pour cela la fonction AES_ENCRYPT()
, qui transforme une donnée lisible en chaîne binaire, illisible sans la clé. Ce chiffrement est réversible, ce qui signifie que la donnée peut être restituée via AES_DECRYPT()
, à condition d’utiliser exactement la même clé.
-- Insertion
INSERT INTO donnees_sensibles (nom, rib)
VALUES ('Géraldine Marchal', AES_ENCRYPT('FR76 3000 6000 0112 3456 7890 189', 'maCleUltraSecrete'));
-- Lecture avec déchiffrement
SELECT id, nom, rib, HEX(rib) AS rib_hex,
CONVERT(AES_DECRYPT(rib, 'maCleUltraSecrete') USING utf8mb4) AS rib_visible
FROM donnees_sensibles;
Le champ rib
contient alors une valeur binaire incompréhensible. Seule une requête munie de la bonne clé permet de la traduire en clair. Cela protège la donnée en cas d’export ou de lecture directe de la base. Mais attention : la sécurité repose ici sur la protection de la clé elle-même. Si elle est codée en dur dans l’application ou accessible en clair sur le serveur, le chiffrement ne protège plus rien. L’idéal est de stocker cette clé hors de la base, par exemple dans une variable d’environnement ou un fichier de configuration protégé.

Le champ rib
s’affiche simplement comme [BLOB - 48 o]
, car phpMyAdmin masque par défaut le contenu binaire. La colonne rib_hex
permet d’en visualiser une représentation lisible, montrant une séquence codée en base 16, différente pour chaque valeur insérée. Enfin, grâce à AES_DECRYPT()
associé à la bonne clé, la colonne rib_visible
restitue la chaîne d’origine, ici un numéro IBAN, de manière parfaitement lisible. Cette triple lecture démontre que la donnée n’est accessible que si la clé de chiffrement est connue, tout en restant techniquement présente et exploitable dans la base.
Audit et relecture des privilèges : garder la main dans le temps
Sécuriser une base MySQL n’est pas une opération ponctuelle, mais un processus à entretenir. Avec le temps, de nouveaux utilisateurs sont ajoutés, des droits sont élargis pour des tests, des comptes temporaires deviennent permanents… et la surface d’exposition augmente sans qu’on s’en aperçoive. Il est donc utile de relire régulièrement les privilèges, pour s’assurer que chaque compte a encore une raison d’exister, et que ses accès sont toujours pertinents.
On peut lister les utilisateurs avec leurs hôtes via SELECT User, Host FROM mysql.user
, ou afficher les droits exacts avec SHOW GRANTS FOR 'utilisateur'@'hôte'
. Repérer un compte oublié, un accès en ALL PRIVILEGES
, ou un @'%'
non justifié permet de corriger avant qu’une faille ne survienne. Une bonne base est une base qu’on connaît encore après plusieurs mois d’utilisation.
Pour auditer les comptes existants, il est utile d’interroger directement la table mysql.user
. En plus des colonnes User
et Host
, on peut consulter les champs plugin
, authentication_string
et parfois Password
, selon la version de MySQL ou MariaDB utilisée. Une requête comme celle-ci permet de repérer les comptes actifs et la manière dont ils s’authentifient :
SELECT User, Host, plugin, authentication_string, Password
FROM mysql.user;

Si les colonnes authentication_string
et Password
sont toutes deux vides, cela signifie que le compte concerné n’utilise pas de mot de passe, ou bien qu’il est relié à un plugin d’authentification externe comme unix_socket
. Ce comportement est fréquent sur certains serveurs MariaDB ou dans des configurations locales permissives. Quoi qu’il en soit, un compte actif sans mot de passe est une faille à corriger immédiatement, surtout s’il est accessible depuis l’extérieur.
Conclusion
Sécuriser une base MySQL ne consiste pas à activer une option unique, mais à adopter une série de gestes cohérents, depuis la création des utilisateurs jusqu’à la gestion des données sensibles. Une base bien protégée limite les droits, contrôle les points d’entrée, chiffre ce qui doit l’être, et conserve une vision claire des comptes actifs. Même en local, même sur un petit projet, ces précautions réduisent fortement les risques d’exploitation ou de fuite.
Cette démarche s’inscrit dans un cadre plus large, où la sécurité ne s’arrête pas à la base : les requêtes SQL elles-mêmes, si elles sont mal formulées, peuvent ouvrir la voie à des attaques. Dans un prochain article, nous aborderons donc la question des requêtes préparées, et verrons comment elles permettent de se protéger efficacement contre les injections.