Sécurisez votre serveur MySQL sous Unix

Jonathan Krier

Cet article fournit de nombreux conseils permettant de sécuriser la configuration et l'utilisation d'un serveur MySQL. Pour chaque méthode proposée, une solution de mise en œuvre sous système Unix est expliquée.

Le but du document est de sensibiliser les développeurs et administrateurs MySQL, tout en leur donnant les connaissances nécessaires pour configurer au mieux la sécurité de ce SGBD.

Pour la lecture de cet article, quelques prérequis sont nécessaires : des connaissances de base en système Unix (gestion des droits utilisateurs, édition de fichiers de configuration…) et en utilisation de MySQL (lancement du serveur, exécution de requêtes…).

- Titre : Sécurisez votre serveur MySQL sous Unix

- Auteur : Jonathan Krier

- Parution : 8 février 2007

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Jonathan Krier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Exécution du serveur : utilisateur système non privilégié

- III. Premières actions de sécurisation

- III-A. Sécuriser l'identification de l'utilisateur root

- III-B. Effacer les comptes anonymes et sans mot de passe

- III-C. Détruire la base de test

- III-D. Le script mysql_secure_installation

- IV. Utiliser efficacement les valeurs d'hôte

- V. Sécuriser l'accès root à distance

- V-A. Interdire complètement l'accès distant

- V-B. Permettre l'accès distant via un tunnel SSH

- VI. Chiffrer les données sensibles dans la base de données

- VII. Maintenir MySQL à jour

- VIII. Une bonne gestion des privilèges des utilisateurs

- IX. Chiffrer les communications client-serveur

- X. Ne garder que les fonctionnalités nécessaires

- XI. Activer et analyser les fichiers de logs

- XII. Spécifier des limites dans MySQL

- XIII. Réflexions sur les mots de passe

- XIV. Quelques conseils d'utilisation en vrac

- XV. Conclusion

- XVI. 17. Ressources complémentaires

- 18. Remerciements

I - Introduction

Les bases de données sont des éléments primordiaux dans un système d'informations. Elles sont incontournables dans de nombreuses entreprises et les données qu'elles contiennent sont souvent d'une importance cruciale. Ignorer la sécurité du système de gestion de base de données serait une erreur grave tant les conséquences d'une détérioration de la base pourraient s'avérer désastreuses.

Ce document explique quelques moyens simples d'améliorer et maintenir la sécurité de MySQL sous système Unix (Linux, BSD…). Il est bien sûr impensable de croire que les conseils de cet article suffiront à obtenir une sécurité optimale.

À l'instar de la sécurisation du SGBD, il faudra veiller à sécuriser le système d'exploitation de la machine hôte, les autres services actifs (serveurs HTTP, FTP…), le réseau, l'accès physique à cette machine et bien sûr vos applications (éviter les failles d'injection SQL).

II - Exécution du serveur : utilisateur système non privilégié

Après installation du serveur MySQL, beaucoup de personnes ont la fâcheuse habitude de lancer le daemon avec les privilèges de l'utilisateur système root. Ce choix peut s'expliquer par une envie d'utiliser la méthode la plus rapide : le serveur MySQL doit lire et écrire dans certains fichiers auxquels l'utilisateur régulier n'a pas accès ; en le lançant avec les privilèges root, on a l'assurance que MySQL aura les droits nécessaires.

Cette méthode d'exécution parait anodine. Cependant, elle peut conduire à des dégâts énormes. Le processus du daemon MySQL tournant avec les privilèges super-utilisateur, il a tous les droits sur le système. Imaginons maintenant qu'un pirate puisse prendre contrôle de ce processus (généralement par exploitation d'une faille de type buffer overflow dans MySQL), il deviendra dès lors le maître absolu de la machine hôte du serveur. Il pourra créer des fichiers, installer des programmes, lire des données confidentielles, ou supprimer des données.

Bien sûr, si une faille est présente dans MySQL, cela n'est pas la faute de l'administrateur. Mais ce n'est pas une raison pour ne pas s'en protéger. Il est impossible d'empêcher un pirate d'exploiter une telle faille. Les seuls à pouvoir le faire sont les développeurs de MySQL AB, qui, dans une telle situation, proposeront une nouvelle version du serveur.

De son côté, l'administrateur du serveur peut limiter les dégâts afin que le pirate ayant pris le contrôle du processus MySQL ne puisse pas détruire autre chose que la base de données (ce qui n'est déjà pas mal !). Le principe est assez simple : créer un utilisateur spécial dont la seule fonction sera de lancer MySQL. Cet utilisateur doit posséder le moins de droits système possible. La solution optimale serait que cet utilisateur n'ait accès qu'aux fichiers propres à MySQL.

Les administrateurs qui ont suivi le fichier INSTALL ou la documentation officielle de MySQL auront remarqué que la création d'un utilisateur « mysql » est préconisée.

Pour les autres, nous allons voir comment y remédier. Les commandes suivantes permettent de créer un groupe système mysql ainsi qu'un utilisateur mysql appartenant au groupe précédemment créé.

# groupadd mysql
# useradd -g mysql mysql

Pour des raisons de sécurité, il est préférable de s'assurer que cet utilisateur ne puisse pas se logger et ne possède pas de home. La commande suivante permet de spécifier ces options sous OpenBSD. Les paramètres de la commande useradd n'étant pas les mêmes d'un système à l'autre, référez-vous au manuel (man useradd) pour obtenir une commande équivalente chez vous.

# useradd -d /nonexistent -s /sbin/nologin -g mysql mysql

Sous Linux, vous pouvez spécifier /bin/false en tant que shell de l'utilisateur pour qu'il ne puisse pas se logger.

Maintenant que notre utilisateur est créé, nous allons lui donner accès en lecture/écriture aux répertoires de données (tables, index, enregistrements…) de MySQL. Pour cela, nous allons le désigner comme propriétaire de ces fichiers. Par la même occasion, nous nous assurons que l'utilisateur système root est propriétaire des autres fichiers de MySQL :

# cd REPERTOIRE_MYSQL
# chown -R root .
# chown -R mysql data
# chgrp -R mysql .

La commande précédente suppose que le dossier de données de MySQL se trouve dans le même répertoire que les autres fichiers de MySQL. Dans le cas contraire, adaptez la commande selon votre installation.

Assurons-nous ensuite que seuls l'utilisateur et le groupe mysql puissent avoir accès aux fichiers de données :

# chmod -R 660 data
# chmod 700 data/mysql

Une fois les droits attribués, nous pouvons lancer le serveur MySQL en spécifiant que l'utilisateur mysql que nous venons de créer sera le propriétaire du processus. Pour cela, deux possibilités s'offrent à nous.

La première est de rajouter une option à la commande permettant de lancer le serveur MySQL :

# cd REPERTOIRE_MYSQL
# bin/mysqld_safe --user=mysql &

La seconde consiste à modifier le fichier de configuration de MySQL (ex : /etc/my.cnf) afin de spécifier que l'utilisateur par défaut pour l'exécution du serveur est mysql. Il ne sera dès lors plus nécessaire de le répéter lors du lancement du serveur.

[mysqld]
user = mysql

Vérifions enfin que notre serveur MySQL tourne avec des droits limités. Pour cela, nous allons utiliser la commande ps après lancement de MySQL.

ps -aux | grep 'mysql'

La première colonne du résultat de la commande indique le propriétaire du processus. Si votre configuration est correcte, vous devriez donc voir mysql.

III - Premières actions de sécurisation

Par défaut, la configuration du service MySQL est loin d'être optimale, aussi bien en termes de performances qu'en termes de sécurité. Quelques réglages sont donc nécessaires. Nous nous limiterons ici aux réglages qui concernent la sécurité, mais je vous invite à vous renseigner sur les réglages d'optimisation possibles.

III-A - Sécuriser l'identification de l'utilisateur root

Lors de l'installation, MySQL crée un administrateur nommé root qui possède tous les privilèges sur la base de données. Cependant, cet utilisateur n'a aucun mot de passe par défaut, laissant la porte grande ouverte aux pirates. Nous allons donc dès maintenant remédier à cela. En plus de spécifier un mot de passe, nous allons également renommer le compte root. Cela rendra encore plus difficile toute tentative de s'approprier les droits d'administration sur le serveur MySQL. En effet, le pirate devra non seulement trouver le mot de passe, mais également le nom d'utilisateur !

Choisissez donc un nom d'utilisateur et un mot de passe relativement compliqués. Pour le nouveau nom du compte root, préférez un choix personnel, et non des mots courants comme "admin", "administrateur" ou "mysql".

Après avoir choisi, connectez-vous au serveur MySQL avec l'utilisateur root et exécutez les requêtes suivantes.

UPDATE mysql.user SET User = 'votre_nouveau_nom_utilisateur', Password = PASSWORD('votre_nouveau_mot_de_passe') WHERE User='root';

FLUSH PRIVILEGES;

Notez l'appel à la fonction PASSWORD() de MySQL qui permet de hacher votre mot de passe avant de l'insérer dans la base de données.

La commande FLUSH PRIVILEGES n'est pas obligatoire, mais elle permet de recharger les privilèges des utilisateurs sans avoir à redémarrer le serveur MySQL.

Une autre méthode de réaliser cette opération de changement de nom d'utilisateur et de mot de passe est la suivante :

SET PASSWORD FOR root@localhost PASSWORD('nouveau_mot_de_passe');

RENAME USER root@localhost TO nouveau_nom_utilisateur@localhost;

Après exécution de l'une de ces requêtes, votre compte root sous MySQL n'existera plus et vous devrez donc utiliser les nom d'utilisateur et mot de passe que vous avez indiqués dans la requête.

III-B - Effacer les comptes anonymes et sans mot de passe

L'installation par défaut de MySQL contient une autre brèche de sécurité : la présence d'un utilisateur anonyme sans mot de passe. Bien que ses droits d'accès soient très limités, il est fortement recommandé de supprimer ce compte. La requête suivante se charge de l'opération :

DELETE FROM mysql.user WHERE User='';

FLUSH PRIVILEGES;

Bien sûr, à aucun moment, il ne faudra créer un utilisateur sans mot de passe, même si cela facilite l'accès à certaines données. Si vous pensez avoir fait une erreur lors de la création d'un utilisateur, exécutez la requête suivante pour supprimer tout utilisateur qui n'a pas de mot de passe :

DELETE FROM mysql.user WHERE Password='';

FLUSH PRIVILEGES;

III-C - Détruire la base de test

Par défaut, MySQL crée une base de test. Celle-ci, certes très utile lors de la mise en place de la structure de vos bases de données, doit être supprimée le plus tôt possible. En effet, elle est accessible par défaut à tout utilisateur, ce qui peut s'avérer très dangereux en terme de sécurité. Vous pouvez bien entendu changer les privilèges d'accès à cette base au lieu de la supprimer.

DROP DATABASE test;

DELETE FROM mysql.db WHERE Db='test' OR Db='test\_%';

Attention : il s'agit bien de l'opérateur = dans la condition Db='test\_%' et non d'un LIKE.

III-D - Le script mysql_secure_installation

Parmi les scripts livrés avec tout serveur MySQL, on peut trouver mysql_secure_installation. Celui-ci est un shellscript UNIX qui permet de réaliser rapidement les actions de sécurisation que nous venons de voir : mot de passe pour le compte root, destruction de la base test et suppression du compte anonyme. Il permet également de désactiver l'accès root à distance. Plusieurs méthodes pour sécuriser cet accès sont présentées plus loin dans cet article.

IV - Utiliser efficacement les valeurs d'hôte

L'une des particularités de MySQL par rapport à d'autres SGBD est que chaque nom d'utilisateur est associé à un hôte (host). Celui-ci permet de définir l'adresse réseau source autorisée pour la connexion d'un utilisateur. Le couple (nom d'utilisateur, hôte) est représenté dans MySQL sous la forme nom_utilisateur@hote.

Il est très important de profiter de cette fonctionnalité pour renforcer la sécurité de l'accès à votre base de données, mais pour éviter toute erreur dangereuse, il faut également connaître le comportement de MySQL pour la gestion de ces hôtes.

Pour s'authentifier à un serveur MySQL, il faut non seulement fournir un nom d'utilisateur correct et le bon mot de passe, mais également se connecter à partir de l'hôte lié à l'utilisateur. En d'autres termes, il faut que le triplet (username, host, password) existe dans la base de données.

La valeur du champ hôte peut être une adresse IP ou un nom d'hôte (ex. : serveur.com). S'il s'agit d'un nom d'hôte, celui-ci devra être traduit en adresse IP. Cette opération est en général réalisée par un serveur DNS.

Il est bien évidemment plus facile d'utiliser des noms d'hôte, sous forme de chaîne de caractères, plutôt que des adresses IP. En termes de sécurité, cela pose cependant un sérieux problème. Si le ou les serveurs DNS sur lesquels vous dépendez sont corrompus, la traduction de noms d'hôte en adresse IP sera faussée, et un pirate pourra ainsi assigner n'importe quelle adresse IP à un nom d'hôte.

De ce fait, il est préférable d'éviter au maximum les noms d'hôte dans MySQL, et de spécifier des adresses IP complètes. Par exemple, pour créer un utilisateur qui pourra se connecter à partir de l'adresse IP 192.168.110.2, on peut exécuter cette commande :

CREATE USER 'toto@192.168.110.2' IDENTIFIED BY 'motdepasse';

Pour les connexions locales, nous n'avons néanmoins pas besoin de spécifier une adresse IP et pouvons utiliser le nom d'hôte localhost. Ce nom d'hôte n'est pas traduit en adresse IP par un serveur DNS, mais la traduction est réalisée localement sur le système. Cela est possible, car le fichier /etc/hosts contient des associations nom de machine / adresse IP et que localhost est associé à l'adresse de loopback 127.0.0.1.

En rajoutant des associations dans /etc/hosts, vous pouvez ainsi utiliser de manière sécurisée des noms d'hôte dans MySQL et vous passer de serveur DNS.

MySQL permet également d'utiliser des jokers (wildcards) dans l'expression des hôtes. Comme pour la clause LIKE, un joker est représenté par les caractères % ou _. Le joker _ signifie « n'importe quel caractère » alors que le joker % signifie « n'importe quelle suite de caractères ».

Le joker % utilisé seul dans un hôte permet ainsi de représenter n'importe quelle adresse source. Cette utilisation est vivement déconseillée.

Spécifier une adresse IP complète comme dans l'exemple précédent n'est bien sûr possible qu'en cas d'utilisation d'adresses IP fixes pour les postes des utilisateurs. Mais si les adresses IP de votre réseau local sont dynamiques (ex. : DHCP), vous pouvez toujours faire mieux que '%', en permettant un nombre restreint d'adresses IP comme en spécifiant l'adresse IP du réseau local. Ainsi, si quelqu'un réussit à voler le mot de passe d'un utilisateur MySQL, il ne pourra se connecter que s'il se trouve sur le même réseau local :

CREATE USER 'developpeur@192.168.110.%' IDENTIFIED BY '...';

Au lieu d'utiliser le joker %, on peut écrire l'adresse IP du réseau couplé à son masque :

CREATE USER 'developpeur@192.168.110.0/255.255.255.0' IDENTIFIED BY '...';

Ces deux exemples ont pour effet de créer un utilisateur developpeur ne pouvant se connecter qu'à partir du réseau 192.168.110.0.

Précédemment, nous avons sécurisé le compte root en lui assignant un mot de passe. En réalité, il y a deux utilisateurs root dans une installation par défaut de MySQL. Cela peut sembler étonnant, mais le fait qu'il existe deux utilisateurs root avec des noms d'hôte différents en font deux administrateurs indépendants, qui peuvent ainsi avoir des privilèges et/ou un mot de passe différents.

Les deux utilisateurs root présents dans une installation par défaut sont :

- root@localhost : l'administrateur se connectant à partir de la machine locale

- root@% : l'administrateur se connectant à partir de n'importe quelle source

Lorsque l'utilisateur root se connecte au service de MySQL à partir de la machine locale, les deux hôtes localhost et % sont valides ; et MySQL doit donc faire un choix. Lors de l'établissement d'une connexion, si plusieurs correspondances existent, c'est la plus explicite qui est choisie. Ainsi pour une connexion locale, c'est localhost qui est privilégié. L'hôte '%' sera dans tous les cas le dernier choix, car il s'agit de l'hôte le moins explicite.

Enfin, avant de terminer ce chapitre, notons que si un utilisateur n'est pas spécifié avec son hôte dans une commande MySQL, l'hôte % sera automatiquement supposé.

Ainsi, les deux commandes suivantes sont équivalentes :

CREATE USER 'toto' IDENTIFIED BY '...';

CREATE USER 'toto@%' IDENTIFIED BY '...';

V - Sécuriser l'accès root à distance

Le compte root sous MySQL est le plus dangereux, puisqu'il correspond au compte DBA, c'est-à-dire à l'administrateur de la base de données. C'est pour cette raison qu'il est nécessaire de prêter une attention toute particulière à la sécurisation de cet accès.

Jusqu'à présent, nous avons spécifié un mot de passe pour le compte root et changé le nom d'utilisateur. En supposant que le mot de passe ne soit pas facilement devinable ou crackable (voir partie « Réflexions sur les mots de passe »), cela constitue une première bonne protection. Mais nous n'allons pas nous arrêter en si bon chemin ! En effet, nous allons aussi interdire tout accès direct au serveur MySQL via l'extérieur.

V-A - Interdire complètement l'accès distant

Si votre serveur MySQL n'est interrogé et administré qu'en local (ex : Apache/PHP et MySQL sur la même machine), le plus sécurisé et le plus rapide est de simplement interdire tout accès provenant d'une autre machine.

Pour cela, nous pouvons configurer un firewall (netfilter / iptables, packet filter…) pour qu'il interdise tout accès entrant vers le port de MySQL, en général 3306.

Cependant, MySQL propose un moyen encore plus simple : ne pas permettre les connexions TCP/IP, mais seulement des connexions faites via un procédé système local, par exemple une socket Unix (la fameuse mysql.sock), des tubes nommés ou par mémoire partagée (sous Windows uniquement).

Afin de désactiver les connexions TCP/IP, il faut démarrer le serveur avec l'option --skip-networking ou bien modifier la partie [mysqld] dans le fichier de configuration du serveur (ex. : /etc/my.cnf) :

[mysqld]
skip-networking

Pour faciliter la communication via socket, il est conseillé de spécifier le chemin d'accès à cette socket dans le ou les fichiers de configuration du serveur et des clients MySQL :

[mysqld]
socket = /tmp/mysql.sock

[client]
socket = /tmp/mysql.sock

Maintenant que nous avons bloqué tout accès distant, nous pouvons également nettoyer la table de privilèges, en supprimant les utilisateurs qui pouvaient se connecter à distance :

DELETE FROM mysql.user WHERE Host <> 'localhost';

FLUSH PRIVILEGES;

V-B - Permettre l'accès distant via un tunnel SSH

Si vous avez obligatoirement besoin d'un accès à distance au serveur MySQL, il existe une méthode relativement bien sécurisée, et surtout valide pour de nombreux programmes client/serveur. Cette solution consiste à utiliser un tunnel SSH (Secure SHell) qui non seulement renforcera l'identification, mais en plus, chiffrera toutes les communications entre le client et le serveur MySQL !

Comme son nom l'indique, le but premier du Secure SHell est de permettre un accès sécurisé à une console (un shell) afin d'exécuter des commandes à distance. Cependant, son assurance de la confidentialité et de l'intégrité des données circulant sur le réseau, lui a également donné une autre fonctionnalité : le tunneling.

Le tunneling, aussi appelé port-forwarding (redirection de port), consiste à utiliser SSH comme une sous-couche dont le but est de sécuriser l'échange de données. En pratique, cela revient à se connecter à un serveur SSH et à ouvrir un port en local. Ce port local est utilisé pour se connecter au service (dans notre cas, à MySQL) et le client SSH redirige les données au serveur SSH, qui lui-même les retransmet au service.

Voyons un exemple pour mieux comprendre.

1. La machine du serveur MySQL est mysql.mondomaine.com. MySQL écoute sur le port 3306.

2. La machine de l'administrateur MySQL est hote1.mondomaine.com. Cet administrateur désire se connecter au serveur MySQL en restant sur cette machine.

3. Sur la machine du serveur MySQL est installé un serveur SSH (exemple : OpenSSH) qui écoute sur le port 25.

4. L'administrateur a installé un client SSH (exemple : OpenSSH ou bien Putty sous Windows) sur sa machine.

5. L'administrateur se connecte à mysql.domaine.com sur le port 22 avec son client SSH. De plus, il configure son client pour gérer un tunnel : le port local 63306 sera utilisé pour forwarder tout trafic à destination du serveur MySQL, c'est-à-dire à destination du port 3306 de mysql.domaine.com.

6. L'administrateur se connecte avec un client MySQL (client console, MySQL Query Browser, MySQL Administrator) à l'adresse localhost : 63306. Le client SSH forwarde tout le trafic vers le serveur SSH (port 22) de mysql.mondomaine.com.

7. Le serveur SSH de mysql.mondomaine.com remarque qu'il s'agit d'un flux "tunnelé". Il redirige celui-ci à destination du port local 3306.

Au vu de ce schéma d'utilisation, nous pouvons déjà remarquer une chose : aucune connexion directe sur le port 3306 du serveur MySQL n'est établie avec le client. Toutes les données provenant du client sont envoyées sur le port SSH du serveur. Pour sécuriser notre serveur MySQL, nous pouvons donc déjà interdire les connexions provenant de l'extérieur.

Nous avons vu comment interdire toute connexion TCP/IP grâce à l'option skip-networking. Cependant, celle-ci ne convient pas dans le cas d'un tunnel SSH. En effet, le serveur SSH doit être capable de rediriger les données vers le serveur MySQL et pour cela, il n'y a pas d'autre solution que TCP/IP. Il est donc nécessaire d'autoriser les connexions TCP/IP locales.

Pour ce faire, nous allons utiliser l'option bind-address qui permet de limiter la provenance des connexions. Voilà ce qui peut être ajouté dans le fichier de configuration de MySQL :

[mysqld]
bind-address=127.0.0.1

Après redémarrage du serveur, seules les connexions provenant de 127.0.0.1 (localhost) seront acceptées. Et puisque nous sommes un peu paranoïaques et surtout perfectionnistes, nous allons également modifier les privilèges de l'utilisateur root (ou autre nom si vous l'avez changé, comme il l'a été conseillé précédemment) sous MySQL :

DELETE FROM mysql.user WHERE User='root' AND Host != 'localhost';

FLUSH PRIVILEGES;

Après exécution de ces requêtes, l'utilisateur root ne pourra s'identifier sur MySQL que s'il est sur la même machine.

Nous n'allons pas voir comment mettre en place (et sécuriser) un serveur SSH, ni comment configurer le client pour activer un tunnel. Je vous conseille cependant de regarder du côté de OpenSSH qui comporte un serveur et un client SSH. Par défaut, le serveur d'OpenSSH (sshd) est configuré pour permettre le tunneling. Pour vous en assurer, vérifier que l'option PermitTunnel est à "yes" dans le fichier de configuration de sshd (en général /etc/ssh/sshd_config)

Voici un exemple de connexion tunnelée en utilisant le client console de MySQL et le client SSH d'OpenSSH :

> ssh -f -L 66306:127.0.0.1:3306 mysql.mondomaine.com sleep 10
admin@host1.mondomaine.com's password : xxxxxxx
=> À partir d'ici, nous sommes connectés au serveur SSH et notre tunnel est activé sur le port local 66306.

> mysql -h 127.0.0.1 -P 66306 -u root -p

=> Pour nous connecter au serveur MySQL, nous utilisons localhost:66306 et non mysql.mondomaine.com:3306

Grâce à ce tunnel, non seulement la communication entre le client et le serveur MySQL sera chiffrée, mais en plus, l'identification sera renforcée puisqu'il faudra également s'identifier sur SSH. À titre d'information, il est possible de désactiver l'identification SSH si le fait d'entrer deux mots de passe vous gêne. Cependant, pensez à sécuriser le compte système utilisé pour la connexion SSH !

Si la machine du serveur MySQL possède également les programmes d'administration (mysql, mysqldump...), il n'est pas nécessaire de mettre en place le port-forwarding. On peut tout simplement lancer les commandes directement sur le shell. Un petit exemple :

> ssh mysql.mondomaine.com /usr/local/mysql/bin/mysqldump -A > backup

VI - Chiffrer les données sensibles dans la base de données

Même si l'accès à votre base de données est sécurisé, les applications qui utilisent votre base de données ne le sont pas forcément. Ainsi, un pirate pourrait détourner le fonctionnement normal de ces applications pour avoir accès à des données stockées dans la base.

Afin d'assurer la confidentialité des données sensibles, il est donc important de les chiffrer lors de l'insertion dans la base de données. Dès lors, si un pirate arrive à lire ces données, il ne pourra rien en faire... à moins d'être capable de les décrypter.

La détermination des données sensibles doit être faite selon des critères propres à votre politique de sécurité. Mais dans tous les cas, il paraît évident de chiffrer des données bancaires, des mots de passe ou des informations personnelles.

MySQL facilite grandement le chiffrement de données, en proposant plusieurs fonctions de chiffrement et de hachage. Pour ceux qui ne le sauraient pas, des données chiffrées peuvent être déchiffrées alors que dans le cas d'un hachage, il est impossible de retrouver les données initiales.

Le hachage est surtout utilisé dans le cas des mots de passe, où pour vérifier l'identification, on compare le "hash" du mot de passe entré avec le "hash" du mot de passe stocké. Si un pirate arrive à récupérer le "hash", il ne pourra pas en déduire le vrai mot de passe ; alors qu'avec un mot de passe chiffré, le pirate pourra éventuellement casser le chiffrement. Un inconvénient des mots de passe hachés est qu'en cas d'oubli, il est nécessaire de réassigner un nouveau mot de passe, puisqu'il n'est pas possible de retrouver le mot de passe à partir du hash.

Voyons les fonctions de hachage disponibles dans MySQL. Tout d'abord, nous avons la fonction de hachage MD5 (Message-Digest Algorithm 5), qui retourne un hash sous forme de chaîne de 32 chiffres hexadécimaux.

mysql> SELECT MD5('mon_mot_de_passe');

+----------------------------------+

| MD5('mon_mot_de_passe')          |

+----------------------------------+

| eda775c5c816d7d1551bf1543ca58090 |

+----------------------------------+

Ensuite, nous avons SHA-1 (Secure Hash Algorithm 1), qui est considéré comme étant plus sécurisé pour les applications cryptographiques. Le hash retourné est également une chaîne représentant un nombre hexadécimal, mais contrairement à MD5, elle est composée de 40 chiffres.

mysql> SELECT SHA1('mon_mot_de_passe');

+------------------------------------------+

| SHA1('mon_mot_de_passe')                 |

+------------------------------------------+

| 18d73442367ccc65a826443db10ff40e2a73454e |

+------------------------------------------+

Attention : MD5 et SHA-1 ne sont pas des solutions parfaites pour assurer la sécurité. Ces dernières années, des failles, relativement sérieuses, ont été exploitées pour les deux algorithmes.

Attention : pour les mots de passe des utilisateurs, MySQL possède sa propre fonction de hachage qui est PASSWORD(). N'utilisez jamais cette fonction pour hacher vos mots de passe applicatifs. Tout d'abord, cette fonction est loin d'être aussi efficace que MD5 ou SHA-1 ; et surtout il est probable que son implémentation change au fil des versions. C'est notamment ce qui est arrivé lors de la sortie de MySQL 4.1. L'ancienne fonction PASSWORD() possédait une faille de sécurité.

Enfin pour les fonctions de hachage, MySQL propose un autre algorithme de chiffrement : celui utilisé pour les mots de passe Unix via la fonction système crypt(). Cette fonction, disponible uniquement si MySQL tourne sous un système Unix (donc pas Windows), combine le MD5 et le DES (décrit par la suite). Le chiffrement est associé à une chaîne d'au moins deux caractères, appelée "salt" (sel en anglais). Si cette chaîne n'est pas spécifiée, une valeur est générée aléatoirement.

L'utilisation de cet algorithme se fait grâce à la fonction ENCRYPT() :

mysql> SELECT ENCRYPT('Message Secret');

+----------------------------------------+

| ENCRYPT('Message Secret')              |

+----------------------------------------+

| Kyl3aJ4CyTzsc                          |

+----------------------------------------+

mysql> SELECT ENCRYPT('Message Secret', 'mon propre sel') AS secret

+------------------------+

| secret                 |

+------------------------+

| mosvrJoNm5dRQ          |

+------------------------+

Si la fonction ENCRYPT() n'est pas disponible sur votre système, elle renvoie toujours NULL.

Voyons maintenant les fonctions de chiffrement/déchiffrement incluses dans MySQL. Commençons par l'algorithme AES (Advanced Encryption Standard). Celui-ci peut être utilisé via les fonctions AES_ENCRYPT() et AES_DECRYPT(), réciproquement pour chiffrer et déchiffrer. Le chiffrement et le déchiffrement se font avec la même clé, l'algorithme AES étant un algorithme de chiffrement symétrique.

mysql> SELECT AES_ENCRYPT('Message tres confidentiel', 'motdepasse');
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aes_1.png

Comme nous pouvons le remarquer, la fonction AES_ENCRYPT retourne une chaîne binaire (type de données BINARY), qui peut donc comporter des caractères non imprimables. Pour déchiffrer, nous utilisons la même clé, c'est-à-dire "motdepasse" dans notre exemple.

mysql> CREATE TABLE testAES SELECT AES_ENCRYPT('Message tres confidentiel', 'motdepasse') AS chaine_chiffree;

mysql> SELECT AES_DECRYPT(chaine_chiffree, 'motdepasse') AS ch_chiffree FROM testAES;

+---------------------------+

| ch_chiffree               |

+---------------------------+

| Message tres confidentiel |

+---------------------------+

Le code précédent crée une table et la remplit avec un enregistrement chiffré par AES. Grâce à l'appel à AES_DECRYPT() en spécifiant la bonne clé, nous pouvons retrouver le message en clair.

AES_DECRYPT() renvoie très souvent NULL lorsque la clé n'est pas bonne. Cependant, ce n'est pas toujours le cas ! La fonction n'est pas capable à 100 % de détecter si la clé est valide ou non. Parfois, AES_DECRYPT() réalise le déchiffrement avec une mauvaise clé, et renvoie donc une chaîne déchiffrée... qui n'est bien sûr pas celle d'origine ! Les deux cas sont illustrés dans les exemples suivants :

mysql> SELECT AES_DECRYPT(chaine_chiffree, 'motdetasse') AS ch_chiffree FROM testAES;

+-------------+

| ch_chiffree |

+-------------+

| NULL        |

+-------------+

Ici, AES_DECRYPT() détecte tout de suite que la clé ne peut pas être la bonne. Il n'essaie pas de déchiffrer. Ce qui n'est pas le cas dans l'exemple suivant :

mysql> SELECT AES_DECRYPT(chaine_chiffree, 'motdemasse') AS ch_chiffree FROM testAES;
iVBORw0KGgoAAAANSUhEUgAAANwAAABQCAIAAAD9Sox1AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAABSBJREFUeF7tnFmS2zAMRCdnyp1ywJwk3znNOKUUC0WAWLgJlDo1H45NgWDjsUkt9o/ff/5+4R8USKXAB0r8QYFUCnylygbJQIF/SzdX4ecvkIrVY74Cfq4A5Xz14XaiAoASqKVToAfKzzH874O8+CbeLxJDn8sX/TqYdGL5TucoT139TRbLwAEloNykAKDcJPRTXW3FuIagXJEQYkIBvwK4JAlPTacAoExXEr+jPLUloASU6RQAlOlK8lT/848LUALKdAoAynQl8TvKU1sCSkCZToEaSv8VztY0ve6Bjk9iM06rl3ITtsqh9f54qohgKhBCYj6U1715M0tPg444CqmeHtFmkQKhUg5BSb2H9nq9Nq2ujD8aR/Q8/1Mq9KkW+ngLz6eSknaxqHhPDbsJyorC1n/NbLrjiJFDTilOGw5igZgSY47rqXj1jSskV79TKt1w11RG0h1nCpQ8sajj9hXpbUcBSuF00k9wyGvfxlb3eO+BsuWOZjat5bI6YeJx/Jy1zr3GI3QX6W0HmhhQQfqXb3qKwPeF1ztlKdRrIJ5A0GOrONUKS09ceCjnclztF/WUxM3o2zgLjXcflKG00PjNCgDKdLco3oyjeO1CF2TOVW6IDgUmKrAJSr6xw7ZsYhUfFmoTlA9TDcNZqgCgxAY0nQKAMl1JlprQEcEBJaBMpwCgTFeSI8xsaZL42RZAuUkB//VzQLmpJEut5YjggBKopVOgB0rx+nb11AW94u180KGkgvZTHhx5gJ4mnVi+0znKEWtxR5ImiyUmoASUmxQAlJuE7jCM1x4yBOVrVcPAkyiAi+fw1HQKAMp0JUliVzemASgBZToFlkBZvjU2Ptv8u+Pxvo6LoOh8tG6AMp1P+OfGdCiToAwoAeV/BUQiqy83+yfMSMu3Q3mXN0z5itJ0p+QkTdyJ+TEd+jGCVjf6SCgHZm10aMzDaYatUOu4pLf7xUwGu+6DsuqUeiH3xZZThpS/nqBIDWWVnImdOH2LKJ7RUjg87UX5eC11lZX25aPuZMqzHa16K5H5R5VNmPR05B8a6ahTijOmNYNF5+jQiIpSCdqa2WLxWk6mrABifMVsqo9K5JDTtPJs6aw7k19wvd+Q/90JZasGijtyqsw1V+xFdzJev6jz0Rq0rKWgJuISKgzFtyVI6YVPFaUvJ5SmnqHhhBqPOqVi9R5d6FwU/aZqQJct8XXIVHhwMZ8yEP6CuwXFsYP71qmGyKiIFwfU3C/pc8wMaC73IU/9NJ4MpciQU2jRXZTtXSVWlADTCZS9bKuKRX2Kb2WfnhIqa0XVtVNw//ZXnHhRqsyK6/mMQlkUD2ktmmjL5KoK0RrrUzxUCZPpVgNOCbdwf1G5nmZiIeWVSV6l3RpXX3dixVuhhqDsI1KpkLlS6A3op57clPYhEas4dKk1R8TXVt61Z7r6WdEdwWkB/u7EKbrQKUOVU5ak6Ag3tH/w0Daod/Py7R/h3Onu77evZQjKs4bWJ8jgUSE9A9fZB9M66/CQiGcNLX+2gFJ+IANQ3sguoDz4KaEbuVnaNaAElOkUAJTpSrLUhI4IDigBZToFAGW6khxhZkuTxM+2AMpNCvgvaADKTSVZai1HBAeUQC2dAj1Q0ntl/EkFfieNty+33quP8D59OgS6mXRi+U7nKEesxR1JmiyWmIASUG5SAFBuErrDMF57yBCUr1UNA0+iAC6ew1PTKQAo05UkiV3dmAagBJTpFACU6Upyo0Ul6RpQAsp0CnwDv1mAkPXQ8TYAAAAASUVORK5CYII=aes_2.png

AES est considéré comme l'algorithme de chiffrement le plus sûr qui est disponible dans MySQL. Cependant, la clé utilisée pour chiffrer est par défaut de 128 bits. Cette clé de 128 bits est générée en utilisant la clé que vous fournissez en paramètre aux fonctions AES_ENCRYPT() et AES_DECRYPT(). MySQL estime que la sécurité est suffisamment bonne avec une clé de 128 bits, et qu'il n'est pas nécessaire d'utiliser une clé plus longue ; ce qui ralentirait les opérations de chiffrement et de déchiffrement.

Il est néanmoins possible d'augmenter la taille de la clé pour accroître la sécurité. L'opération n'est pas évidente à réaliser, car elle nécessite une modification du code source ainsi qu'une recompilation. Si vous voulez augmenter la taille de la clé à 256 bits (taille maximum pour l'AES), il vous faut éditer le fichier include/my_aes.h et plus exactement la définition du symbole AES_KEY_LENGTH :

#define AES_KEY_LENGTH 128 /* Mettez soit 128, 192 ou 256 */

Après avoir effectué cette modification, reportez-vous à la documentation officielle de MySQL pour recompiler les sources, en fonction de votre système d'exploitation.

Terminons notre description de l'AES par une petite constatation. Cet algorithme chiffre par bloc de 16 octets (128 bits) et par conséquent, des octets de rembourrage (padding) sont ajoutés à la chaîne à chiffrer pour obtenir toujours un multiple de 16 octets. De ce fait, AES_ENCRYPT() renvoie toujours une chaîne binaire dont la longueur est un multiple de 16. Chose amusante avec MySQL : une chaîne en clair de longueur multiple de 16 octets prendra 16 octets supplémentaires une fois chiffrée (exemple : une chaîne de 32 octets en fera 48 une fois chiffrée) ! Cela n'est pas le cas dans toutes les implémentations d'AES, faites donc attention quand vous travaillez avec d'autres bibliothèques et langages.

Passons maintenant à un autre algorithme de chiffrement : le 3-DES (Triple Data Encryption Standard). Celui-ci est l'ancêtre de AES, et est donc moins bien sécurisé !

Le principe est quasiment identique. Il s'agit d'un algorithme de chiffrement symétrique, et donc la même clé est utilisée pour chiffrer et déchiffrer. Contrairement à AES, un fichier de clés par défaut peut être utilisé. Cela évite de spécifier la clé à chaque opération de chiffrement / déchiffrement.

Les fonctions pour utiliser le 3-DES sont DES_ENCRYPT() et DES_DECRYPT(). Le premier paramètre est la chaîne à chiffrer ou déchiffrer. Le deuxième paramètre est optionnel et peut soit être une chaîne de caractères qui servira de clés, soit le numéro de la clé (du fichier de clés) à utiliser. Si ce deuxième paramètre n'est pas fourni, c'est la clé n°0 du fichier qui sera prise en compte.

Voyons quelques exemples :

mysql> SELECT DES_ENCRYPT('Message secret', 'motdepasse');
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_1.png

mysql> SELECT DES_ENCRYPT('Message secret', 4);
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des_2.png

mysql> SELECT DES_ENCRYPT('Message secret');
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des_3.png

Dans le premier cas, nous spécifions la clé. Dans les deux autres, nous utilisons une clé disponible dans le fichier de clés par défaut : la clé n° 4 est utilisée pour le deuxième exemple, et la clé n° 0 est choisie pour le troisième.

Comme nous pouvons le remarquer, un intérêt du fichier de clés est que la clé n'est pas visible dans les requêtes ! Si quelqu'un arrive à avoir accès aux logs de requêtes, il ne verra pas la clé, ce qui est une très bonne chose.

Ce fichier de clés peut donc être très intéressant. Par défaut, MySQL utilise son propre fichier de clés, mais il est tout à fait possible d'en spécifier un autre. Cela se fait grâce à l'option --des-key-file=nom_fichier lors du démarrage du serveur MySQL.

Le format du fichier est le suivant : une ligne par clé où chaque ligne est "num_clé clé". Le nombre de clés est au maximum dix, et par conséquent num_clé doit être compris entre 0 et 9. Voyons un exemple de fichier de clés :

0 cleParDefaut
3 quatriemeCle
1 motDePasse

Bien évidemment, si vous stockez des clés DES dans un fichier externe, il faudra veiller à sécuriser l'accès à ce fichier ! Cela vaut de même si vous stockez des clés (peu importe l'algorithme utilisé) dans vos codes sources. Il est facile de protéger des données grâce au chiffrement, mais encore faut-il protéger correctement la clé.

Attention : les fonctions de 3-DES ne sont disponibles dans MySQL que si celui-ci a été compilé avec le support SSL. Si vous avez téléchargé une version binaire officielle, c'est normalement le cas.

Et enfin, voyons les deux dernières fonctions de MySQL permettant de chiffrer et déchiffrer : ENCODE() et DECODE(). Ces fonctions ne correspondent pas à un algorithme de chiffrement connu (et reconnu). Il n'est donc pas recommandé de les utiliser pour sécuriser des données hautement confidentielles. Cependant, pour un chiffrement de base, ces deux fonctions s'exécutent bien plus rapidement que l'AES ou le 3-DES.

Les deux fonctions prennent les mêmes paramètres : la chaîne à chiffrer/déchiffrer et le mot de passe à utiliser pour l'opération.

mysql> SELECT ENCODE('Message secret', 'motdepasse') AS ch_chiffree;
iVBORw0KGgoAAAANSUhEUgAAAJQAAABMCAIAAAAEMwnBAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAA6ZJREFUeF7tW1tOxDAMhDNxJw7ISfjmNKxUKRi/kjivencQH0u3dWyPZ5y65f3r++cNP0kz8AAPv0kz8Eb9/vj892fSkJ7PbQsXgJdAeABeApAszfDAe3wnfx+G1IM4XlK5Pz8MRchmAkZCNhOAFJHNcg12m/fcqTYx756uwysrA7ixSyynAA/gYcZ2IgNgHph3ou6wkQHzwDww70QG/pg3fod+zfrG1axqx1qlDBuZD9bxcVc3WHBSOhO8a2Y9JZ6AHQfRKS6dMjIfPFrL1Pr1uUodOpArJ7fYUTkUm+7TxyPSH5YyusRmFCeDx7Js/VllT9iOarmLeWp5ScAunKzje1BcCJ7sLu1jbsctycLq6xq94MnU9zIY4Ok79VPgqXhUJWQpimuZZyW6GrMjRwCvRcCCu021gdPdhLqz8JXK2sXQ/sSUrfSkkeNMkP3Q1GaZiXlLfYVxZxvBvgoyDyneloGmnrfNGyw0KwNzBiKON3L/vb9tzErW3ewsB+9uAT+TPwAPj4ROPBB5Jg7FYgHzwDww70QG8L8KCZhn3eoBPIB3QjRiTT7jVR7z1PvoR5C9z7dw/pRBeYHKwqXUH2QTsgnZXJkBbFgSMMzqx03gZWzmr+wzJiyJGQnwAN7Kdv3KwujHDuaBeYR51Tf+7s+kLCEsYV6W4NUyupxPEcIS8HrvV9q5WN5/mZLcQSPO5XP9tPIz+uqffLnWer9IDZUG2ZLKQgt2Mp0HNpaCZBiNxYqLRueDV4bDjf4EKn4IPMv7FhjoDHdWnINpYmpJo2j5PL66I+PqV/PBU3uGLOQCHvvgPM1gX9F4VPsOrav+UCCrDJOZLfbb63gf86hzqoJZIslc7JI7SyqLTbmor3Lq+dKaD4BvZHzv46y+iXmSXo7IsMpgkuUUdSMYPtg+eGov9JlHC9Qp+n3Mc8CwFGl6DVZL3ulhlv9qb/MZX3VjUDn3MU8N3qn0riYvKWiJsLpiNcuBDYtaBFU/w1GzC2fKJuUc419ALqytV2ljrJ+pcmpprOOPpRxdiiJT0QUY24tZ10bAc8IIu7jowkHJavRq6SqrZLMxtlOnJSoyf/tWZ96pFGPdcAa2zjbDXuJCNQMAD8/z8CT9RAbAPDDvRN2hEYJ5z8K8pTebIEo4AxYu+EeTBMwDeAlAsqjpgcemtGzyK2e78vwyXFcHvjh/Vn4YipDNBIyEbCYAKSKbsx6WhndTuNDPQBPzkMRcGcBNemI5BXiJwfsFIs6bTl2TQs4AAAAASUVORK5CYII=encode_1.png

mysql> SELECT DECODE(ENCODE('Message secret', 'motdepasse'), 'motdepasse') AS ch_claire;

+----------------+

| ch_claire      |

+----------------+

| Message secret |

+----------------+

VII - Maintenir MySQL à jour

Comme tout programme, MySQL n'est pas parfait. Des failles sont très probablement présentes, risquant de compromettre votre installation. Pour cette raison, il est nécessaire de vérifier régulièrement si une nouvelle version n'est pas disponible, corrigeant des bogues liés à la sécurité.

MySQL AB propose une newsletter qui vous permettra de rester informés des nouvelles versions du serveur et des utilitaires officiels.

Il n'est pas toujours aisé de migrer d'une version à une autre, surtout lors d'un changement de version majeure (ex. : 4.1 => 5). Mais pour la sécurité, il n'est pas nécessaire d'utiliser forcément la dernière version majeure. En effet, MySQL AB continue à assurer les mises à jour de sécurité pour la version majeure précédente pendant plus d'un an.

VIII - Une bonne gestion des privilèges des utilisateurs

La sécurité d'une base de données passe également par une gestion réfléchie des privilèges des utilisateurs. Le langage SQL propose deux mots clés permettant de gérer les privilèges : GRANT et REVOKE. Nous allons apprendre à les utiliser ici, avec les spécificités de MySQL.

Tout d'abord, il est important de rappeler la règle d'or pour une gestion sécurisée des droits utilisateurs : un minimum de droits = un maximum de sécurité. En d'autres termes, il est complètement inutile d'attribuer plus de privilèges que ceux nécessaires pour réaliser les opérations prévues. Tout privilège excessif peut donner lieu à une faille de sécurité.

Ainsi, il est évident que, dans les applications qui utilisent la base de données, l'utilisateur root (ou tout autre administrateur) ne doit pas être utilisé. Il faut donc créer un ou plusieurs utilisateurs sous MySQL qui n'auront que les droits correspondants aux opérations que permet l'application.

La commande GRANT permet d'attribuer des privilèges. La plupart des privilèges peuvent concerner une colonne, une table entière ou une base de données. Certains peuvent même être globaux, c'est-à-dire valides pour toutes les tables et toutes les bases de données. Des privilèges s'appliquent également aux routines : procédures stockées, fonctions et triggers.

Voyons brièvement quelques exemples de la commande GRANT. Pour plus d'informations concernant la syntaxe et les privilèges disponibles dans MySQL, référez-vous à la documentation officielle.

Créons tout d'abord une base de données contenant deux tables pour nos tests :

mysql> CREATE DATABASE mabase;

mysql> USE mabase;

mysql> CREATE TABLE testGrant1 (id INT, lib VARCHAR(20));

mysql> CREATE TABLE testGrant2 (code VARCHAR(3), prix DECIMAL(5,2));

mysql> INSERT INTO testGrant1 VALUES (1, 'Test'), (2, 'Retest'), (3, 'OK');

mysql> INSERT INTO testGrant2 VALUES ('AAA', 50.23), ('BBB', 24.20);

Nous allons maintenant accorder des privilèges à trois utilisateurs : le premier pourra exécuter des SELECT sur la table testGrant2, mais uniquement pour la colonne code, le deuxième pourra seulement insérer dans les deux tables et le troisième pourra réaliser des SELECT, INSERT, UPDATE et DELETE dans toute la base.

mysql> GRANT SELECT (code) ON mabase.testGrant2 TO 'utilisateur1'@'%' IDENTIFIED BY 'pass1';

Ici nous donnons donc le droit SELECT sur la colonne code de la table testGrant2, qui appartient à la base de données mabase, à l'utilisateur "utilisateur1" qui peut se connecter de n'importe quel host ('%') et dont le mot de passe est "pass1". Si l'utilisateur n'existe pas, la commande GRANT se charge de le créer. C'est la raison pour laquelle nous spécifions le mot de passe de l'utilisateur.

Connectons-nous maintenant à MySQL avec ce nouvel utilisateur et testons quelques requêtes :

mysql> SELECT * FROM mabase.testGrant2;

ERROR 1143 (42000): SELECT command denied to user 'utilisateur1'@'localhost' for column 'prix' in table 'testgrant2'

mysql> SELECT code FROM mabase.testGrant2;

+------+

| code |

+------+

| AAA  |

| BBB  |

+------+

Nous pouvons donc remarquer que MySQL empêche bien le SELECT sur une autre colonne que code, c'est-à-dire la colonne prix pour notre exemple.

Passons aux privilèges du deuxième utilisateur :

mysql> GRANT INSERT ON mabase.* TO 'utilisateur2'@'%' IDENTIFIED BY 'pass2';

Nous lui donnons le droit d'INSERT dans les deux tables de mabase.

mysql> SELECT * FROM mabase.testGrant1;

ERROR 1142 (42000): SELECT command denied to user 'utilisateur2'@'localhost' for table 'testgrant1'

mysql> DELETE FROM mabase.testGrant2;

ERROR 1142 (42000) : DELETE command denied to user 'utilisateur2'@'localhost' for table 'testgrant2'

mysql> INSERT INTO mabase.testGrant1 VALUES (1, 'allo ?');

Query OK, 1 row affected (0.00 sec)

L'insertion fonctionne, mais toute autre opération (SELECT, DELETE…) échoue. C'est exactement ce que nous voulons.

Pour le troisième utilisateur, nous lui donnons les droits pour les opérations courantes :

mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON mabase.* TO 'utilisateur3'@'%' IDENTIFIED BY 'pass3';

Dans la pratique, on peut imaginer que l'utilisateur n° 1 soit utilisé par une application qui a juste besoin de lister les codes de testGrant2 ; que l'utilisateur n° 2 soit utilisé par une application dont le but est de remplir les tables, sans avoir besoin de les lire ; et que l'utilisateur n° 3 soit réservé pour une autre application utilisée par des personnes plus privilégiées.

Ces exemples montrent quelques possibilités de GRANT qui simplifient parfois bien la vie des administrateurs de bases de données. Cependant, ce ne sont pas forcément des habitudes à prendre quand on désire sécuriser au maximum l'accès aux données.

Voyons donc ce qu'il faut éviter. Tout d'abord, comme nous l'avons déjà vu, l'utilisation de '%' pour les hostnames est tout simplement à proscrire, car il permet à un utilisateur de se connecter de n'importe où. Si vous avez mis en place un tunnel SSH ou si vous avez désactivé les connexions TCP/IP (--skip-networking), cela n'est pas bien grave. Dans le cas contraire, veillez à toujours spécifier un hôte le plus complet possible.

Seconde réflexion à propos de la sécurité dans nos exemples précédents : l'utilisation de * pour attribuer des droits à plusieurs tables avec un seul GRANT. Là encore, c'est très pratique. Pour l'utilisateur n° 2, nous voulions lui attribuer le privilège INSERT sur les deux tables, et comme notre base ne comportait que ces deux tables, l'utilisation de mabase.* semblait parfaite. Or, si maintenant nous rajoutons d'autres tables, l'utilisateur n° 2 pourra également insérer dedans ! Il vaut donc mieux perdre quelques secondes pour faire plusieurs GRANT que risquer de donner dans le futur des privilèges non souhaités.

S'il n'y avait qu'une seule phrase pour résumer l'utilisation judicieuse de GRANT, elle serait la suivante : limiter le plus possible les privilèges pour qu'ils correspondent uniquement aux besoins ; et utiliser le moins possible les jokers (en anglais : wildcards, c'est-à-dire les caractères * et %).

Un moyen simple d'obtenir des privilèges adaptés aux besoins est de créer des vues (depuis MySQL 5) réalisant les SELECT fréquents, et ne donner les droits que sur les vues et non sur les tables directement. En cas de mise à jour de la requête, il suffira de modifier la vue. C'est donc plus simple que de devoir modifier les privilèges des utilisateurs, surtout si plusieurs utilisateurs ont le droit d'exécuter ces SELECT.

Imaginons une table avec dix colonnes (col1, col2…, col10). Nous désirons limiter l'accès en lecture aux cinq premières colonnes pour un utilisateur. Pour ceci, nous créons une vue et donnons le privilège SELECT sur cette vue à l'utilisateur concerné.

mysql> CREATE VIEW vue_req1 AS SELECT col1, col2, col3, col4, col5 FROM uneTable;

mysql> GRANT SELECT ON mabase.vue_req1 TO 'utilisateur1'@'localhost';

Cette méthode est même plus sécurisée que de donner les privilèges de SELECT sur chaque colonne, car grâce à la vue, l'utilisateur ne peut pas connaître les champs des tables auxquels il n'a pas accès.

Dans notre exemple, la vue restreint le nombre de colonnes accessibles. Il est bien sûr possible d'utiliser une vue pour restreindre le nombre de lignes accessibles, et cela grâce à une clause WHERE.

Voyons maintenant la commande REVOKE qui permet de révoquer des privilèges. Sa syntaxe est très proche de celle de GRANT. Si par exemple, nous voulons supprimer le privilège DELETE à un utilisateur sur toutes les bases et tables auxquelles il a accès, nous pouvons exécuter la commande suivante :

mysql> REVOKE DELETE ON *.* FROM 'utilisateur1'@'localhost';

Grâce aux commandes GRANT et REVOKE, vous pouvez donc gérer les privilèges de vos utilisateurs. Une autre commande intéressante à connaître est SHOW GRANTS qui permet à un utilisateur de voir les privilèges qui lui ont été accordés. En tant qu'administrateur, vous pouvez également utiliser cette commande pour lister les privilèges octroyés à un utilisateur spécifique :

mysql> SHOW GRANTS FOR 'utilisateur1'@'localhost';

Il est vivement conseillé d'afficher la liste des privilèges octroyés après une modification de ceux-ci. En effet, certains détails pourraient vous échapper et ainsi laisser des privilèges actifs alors que vous pensiez les avoir supprimés. Prenons un exemple qui pourrait s'avérer dangereux.

Imaginons que vous ayez créé un utilisateur toto@localhost et que vous lui donniez un privilège INSERT sur votre table produits de la base catalogue :

GRANT INSERT ON catalogue.produits TO toto@localhost;

Tout fonctionne à merveille pendant plusieurs mois, jusqu'au jour où vous décidez d'octroyer à toto un privilège d'insertion sur toute la base catalogue. On peut par exemple imaginer que votre application qui fait ses requêtes avec l'utilisateur toto vient d'être dotée de nouvelles fonctionnalités.

Vous exécutez dès lors la commande suivante :

GRANT INSERT ON catalogue.* TO toto@localhost;

Si vous affichez les privilèges donnés à cet utilisateur, vous pouvez remarquer que MySQL n'a pas regroupé les deux privilèges INSERT :

SHOW GRANTS FOR toto@localhost;

+--------------------------------------------------------------+

| GRANT USAGE ON *.* TO 'toto'@'localhost' ...                 |

| GRANT INSERT ON `catalogue`.* TO 'toto'@'localhost'          |

| GRANT INSERT ON `catalogue`.`produits` TO 'toto'@'localhost' |

+--------------------------------------------------------------+

Si maintenant vous révoquez le privilège INSERT sur la base entière, l'ancien privilège INSERT restera !

REVOKE INSERT ON catalogue.* FROM toto@localhost;

SHOW GRANTS FOR toto@localhost;

+--------------------------------------------------------------+

| GRANT USAGE ON *.* TO 'toto'@'localhost' ...                 |

| GRANT INSERT ON `catalogue`.`produits` TO 'toto'@'localhost' |

+--------------------------------------------------------------+

Révoquer un privilège qui s'applique à une base (nom_base.*) ou qui est global (*.*) n'est donc pas équivalent à supprimer tous les privilèges inclus !

Certains privilèges sous MySQL sont très dangereux. Il vaut donc mieux éviter de les attribuer. Ceux-ci sont pour la plupart des privilèges réservés à des tâches d'administration :

- SUPER : permet notamment de tuer une connexion ou d'avoir accès à des informations de débogage concernant le serveur MySQL ;

- FILE : permet d'utiliser les commandes LOAD DATA INFILE et SELECT ... INTO OUTFILE. Ces commandes sont dangereuses, surtout si l'utilisateur exécutant MySQL possède des droits d'écriture et de lecture en dehors des fichiers de la base de données. On peut imaginer que LOAD DATA INFILE soit utilisée pour lire un fichier présent sur le disque dur (ex. : /etc/passwd) ou que SELECT ... INTO OUTFILE soit utilisée pour écrire dans un fichier exécutable (ex. : placer des commandes dans .bashrc) ;

- GRANT OPTION : ce privilège, en général octroyé sous forme d'option lors de la commande GRANT, permet à un utilisateur de donner les privilèges qu'il possède avec l'option GRANT OPTION à d'autres utilisateurs, ainsi que de les supprimer. Ce privilège est donc très dangereux, et ne doit jamais être attribué de manière globale.

Voici ce qu'il faut principalement éviter :

mysql> GRANT GRANT OPTION ON *.* TO utilisateur;

- SHOW DATABASES : permet à un utilisateur de lister toutes les bases de données existantes. Il n'y a aucun intérêt à donner ce genre d'infos, sauf dans le cas d'applications spécifiques ;

- RELOAD : permet à un utilisateur de rafraîchir ou de vider des caches (données, privilèges…) de MySQL. Ici encore, ce privilège doit être limité à un administrateur qui en a besoin ;

- SHUTDOWN : ce privilège donne la possibilité à un utilisateur de fermer le serveur MySQL, via la commande "mysqladmin shutdown" ;

- PROCESS : permet de voir tous les processus (en fait des threads) en cours via la commande SHOW FULL PROCESSLIST. Grâce à ce privilège, on peut voir toutes les connexions établies, la requête en cours d'exécution pour chaque connexion... Bref, beaucoup d'informations qui pourraient intéresser un pirate.

De même, si vous utilisez la réplication, il vaut mieux limiter les droits des utilisateurs esclaves. Il est recommandé que les utilisateurs esclaves aient seulement les privilèges pour faire de la réplication (privilège REPLICATION SLAVE). Il n'est donc pas nécessaire de donner accès aux commandes LOAD [DATA | TABLE] FROM MASTER si cela n'est pas utilisé. À noter que ces commandes sont même maintenant dépréciées ;-)

IX - Chiffrer les communications client-serveur

Par défaut, toute communication entre un client et un serveur MySQL circule en clair sur le réseau... à une petite exception près : le mot de passe lors de l'identification. Un pirate capable d'intercepter les flux du réseau pourra donc voir toutes les requêtes exécutées ! Le seul moyen d'éviter cela est de chiffrer les communications. Bien évidemment, si tout accès à votre serveur MySQL se fait localement (et que vous avec donc ajouté l'option --skip-networking comme conseillé précédemment), il n'est pas nécessaire de lire ce qui va suivre.

Lorsque nous avons parlé de l'accès administrateur à distance, nous avons vu brièvement comment utiliser SSH pour mettre en place un tunnel sécurisé. Grâce à celui-ci, les communications étaient chiffrées. L'inconvénient de cette méthode est que chaque client doit mettre en place le "port forwarding" pour le tunnel. Pour l'administrateur, cela n'est pas bien grave puisqu'il est fort probable qu'il utilisera SSH pour d'autres opérations (ex. : gestion du système d'exploitation à distance).

Au lieu de compter sur SSH pour sécuriser nos transmissions, nous pouvons coupler notre serveur MySQL avec le protocole SSL (Secure Sockets Layer). Ainsi, les communications seront chiffrées / déchiffrées directement au niveau de MySQL, et il ne sera pas nécessaire de mettre en place un tunnel sécurisé.

L'intérêt d'utiliser SSL n'est pas seulement de chiffrer les communications : il offre également une méthode d'authentification plus sécurisée qu'un simple système de mots de passe ; ainsi que l'assurance de l'intégrité des messages transitant entre le client et le serveur, très pratique pour être sûr que la requête exécutée est bien celle émise par le client.

En résumé, SSL offre trois grands fondements de la sécurité : confidentialité, intégrité et authentification. En voyant tous ces avantages, vous vous demandez sûrement pourquoi il n'est pas activé par défaut sur MySQL. La réponse est assez simple : SSL nécessite des traitements supplémentaires et réduit donc les performances. Et puisque les performances sont sacrées dans le domaine des bases de données, les éditeurs de SGBD préfèrent, à juste titre, oublier cette notion de sécurité renforcée dans leur configuration par défaut.

Nous allons maintenant voir comment profiter de SSL avec MySQL. Mais avant tout, il est nécessaire de connaître les notions de base en PKI (Public Key Infrastructure) et de SSL.

Dans une PKI, chaque utilisateur possède une clé privée et une clé publique, qui sont toutes deux générées par un algorithme spécifique. Cet algorithme crée les deux clés de manière à ce qu'il existe un lien mathématique entre elles.

Comme son nom l'indique, la clé publique peut être transmise à d'autres personnes ; cela ne mettant aucunement en péril la sécurité. Par contre, la clé privée doit rester secrète, car elle est utilisée pour les opérations primordiales.

Les clés permettent principalement de réaliser un chiffrement asymétrique. Nous avons vu précédemment les algorithmes AES et 3-DES qui réalisent un chiffrement symétrique ; c'est-à-dire qu'ils utilisent la même clé pour chiffrer et pour déchiffrer. Le chiffrement asymétrique permet quant à lui d'utiliser deux clés : une clé de chiffrement et une clé de déchiffrement. Ceci n'est possible que grâce aux propriétés mathématiques existant entre les clés privées et publiques.

L'intérêt d'utiliser deux clés est de combler l'inconvénient majeur du chiffrement symétrique : la clé unique doit être échangée entre les interlocuteurs de manière sécurisée ; et cela est loin d'être facile, surtout sur Internet. Avec le chiffrement asymétrique, les clés publiques (permettant le chiffrement) sont échangées sans crainte ; alors que les clés privées restent chez leur propriétaire respectif, qui les utilisent pour déchiffrer les messages qu'ils reçoivent.

Le chiffrement asymétrique est souvent lié à une autre opération très intéressante en sécurité : la signature numérique. On peut comparer cette signature à une empreinte ajoutée aux données transférées. Grâce à sa clé privée, un utilisateur peut signer numériquement ses documents ou textes. Le destinataire utilisera la clé publique de l'émetteur pour vérifier que la signature est valide, et sera ainsi certain qu'il a bien reçu les données de la bonne personne.

Afin de bien comprendre le principe de la cryptographie asymétrique et de la signature numérique, prenons l'exemple de deux utilisateurs A et B, ayant préalablement échangé leur clé publique. A veut envoyer de manière sécurisée des données à B :

- pour que B puisse s'assurer de l'authenticité de l'émetteur (A), les données sont signées par A avant l'envoi. Pour ce faire, A utilise sa clé privée. Pour vérifier la signature de A, B utilisera la clé publique de A ;

- pour que les données ne circulent pas en clair, A décide de les chiffrer lors de l'envoi. Pour cela, il utilise la clé publique de B. Le déchiffrement ne sera possible qu'avec la clé privée de B.

En résumé, toute opération de signature et vérification de signature utilisera les clés de l'émetteur. Alors que toute opération de chiffrement / déchiffrement utilisera les clés du destinataire.

Voilà pour le respect de l'authenticité et de la confidentialité. Il ne reste plus qu'à assurer l'intégrité des données. Là, rien d'exceptionnel, l'émetteur calcule un hash des données et l'envoie avec les données. À la réception, le hash est recalculé avec les données reçues et est comparé avec le hash reçu. S'ils sont identiques, les données n'ont pas été altérées. En général, la signature numérique englobe ce hash de contrôle d'intégrité.

Maintenant que nous savons comment assurer la confidentialité, l'intégrité des données et l'authentification, nous pouvons introduire une nouvelle notion : les certificats. Comme nous l'avons vu, les clés publiques sont destinées à être échangées. Mais une clé n'est en fait qu'une suite de chiffres et afin de savoir à qui elles appartiennent, il faut les lier à des informations personnelles sur leur propriétaire.

C'est là que les certificats interviennent. On peut comparer un certificat à une carte d'identité, qui contient donc une clé publique, mais aussi le nom et la localisation géographique du propriétaire de cette clé. Un problème persiste néanmoins : lors de la réception d'un certificat, quelle assurance avons-nous qu'il provienne bien de la personne nommée dans le certificat ?

Dans une PKI, il n'y a pas que des utilisateurs. Un autre acteur est important : l'autorité de certification. Son rôle est de s'assurer que les certificats des utilisateurs sont dignes de confiance. L'autorité de certification (appelée CA : Certificate Authority) joue donc le rôle de tiers de confiance dans une PKI et c'est elle qui est chargée de produire les certificats.

La CA doit réaliser un contrôle d'identité sérieux avant d'émettre les certificats aux utilisateurs. On remarque donc qu'elle est un point névralgique de la PKI. Si elle est compromise, la PKI devient complètement inutile !

Afin que les utilisateurs puissent vérifient la validité des certificats, la CA y apporte sa signature. Et comment vérifier la validité et l'authenticité de cette signature ? Par un certificat pardi ! Rassurez-vous, ce n'est pas une boucle infinie. Une PKI est plutôt une hiérarchie (arbre) dans laquelle la CA se trouve à la racine. C'est pour cela que le certificat de la CA s'appelle le « root certificate » (certificat racine). L'utilisateur doit être certain quand il installe un « root certificate » que celui-ci appartient bien à la CA.

Il est fort à parier que vous avez déjà utilisé des PKI, peut-être sans même vous en rendre compte. Mais à l'heure actuelle, tout site e-commerce ou tout service e-banking sérieux vous envoie son certificat quand vous vous préparez à réaliser une opération importante (paiement en ligne, accès à vos comptes...). Pour vérifier ces certificats, il vous faut donc le « root certificate » de la CA qui les a émis. En général, vous n'avez pas besoin de les installer, car votre navigateur préféré contient dès son installation les certificats des grandes CA.

Nous en savons assez à propos des PKI. Passons maintenant aux fonctionnalités qu'offrent MySQL et SSL.

SSL utilise plusieurs algorithmes qui lui permettent de détecter toute modification, perte ou renvoi de données sur le réseau. Pour authentifier les acteurs de la PKI, SSL utilise des certificats X509.

Pour faire fonctionner MySQL avec SSL, il est nécessaire qu'il ait été compilé avec le support de SSL, soit via la bibliothèque OpenSSL, soit via yaSSL. Au cas où vous seriez un adepte de la compilation de vos applications, il faut utiliser l'option --with-openssl ou --with-yassl lors du ./configure.

Afin de savoir si un serveur MySQL possède le support pour SSL, il suffit de s'y connecter et de taper la requête suivante :

mysql> SHOW VARIABLES LIKE 'have_openssl';

+---------------+----------+

| Variable_name | Value    |

+---------------+----------+

| have_openssl  | DISABLED |

+---------------+----------+

Si la variable have_openssl indique DISABLED comme c'est le cas dans l'exemple, cela signifie que le support est disponible, mais non activé. Nous allons donc voir comment le rendre actif !

Pour démarrer un serveur MySQL avec SSL activé, trois fichiers sont nécessaires en paramètres :

- la clé privée du serveur (option -ssl-key) ;

- la clé publique / certificat du serveur (option -ssl-cert) ;

- le certificat de la CA (option -ssl-ca).

Pour le client, c'est un peu différent. En fait, il faut savoir que MySQL permet de faire du cas par cas. En d'autres termes, il est possible de n'activer SSL que pour certains utilisateurs ou encore de permettre des communications sécurisées avec un utilisateur sans vérifier l'authenticité du client. Dans ce dernier cas, le client n'a donc pas besoin de fournir son certificat.

La configuration des paramètres SSL pour les utilisateurs se fait lors de l'octroi des privilèges (GRANT).

Si un client n'a pas besoin de s'authentifier par certificat, il suffit de lancer le client avec le certificat de la CA (option --ssl-ca). Par contre, s'il a besoin de s'authentifier, il faudra également sa clé privée (option --ssl-key) et sa clé publique (option --ssl-cert). Bien évidemment, le certificat de la CA doit être le même sur le client et le serveur !

Avant de passer à un exemple d'utilisation de SSL, voyons comment générer les clés et les certificats. Dans ce but, nous allons nous aider d'OpenSSL et pour simplifier, nous jouerons les rôles de la CA, du serveur et du client. Pour être utilisés par MySQL, tous les certificats doivent être dans le format PEM, c'est-à-dire être des certificats X509 encodés en base-64.

Commençons par créer une clé privée et un certificat pour l'autorité de certification :

openssl req -new -x509 -keyout ca-key.pem -out ca-cert.pem -days 3600

Pour compléter la création des deux fichiers (clé et certificat), il faut répondre aux questions posées par OpenSSL. Tout d'abord, il demande une passphrase : un mot de passe protégeant l'accès à la clé privée. Ensuite, il faut lui fournir différentes informations sur l'identité de notre CA (pays, email du responsable…)

Maintenant, nous générons la clé privée du serveur ainsi qu'une demande de certificat (certificate request).

openssl req -new -keyout server-key.pem -out server-req.pem -days 3600

Dans une vraie PKI, le fichier de demande de certificat (ici server-req.pem) devrait être envoyée à la CA qui générerait un certificat à partir de ce fichier. Dans notre exemple, nous allons donc utiliser la clé privée et le certificat de la CA. La passphrase entrée pour la clé privée de la CA devra bien évidemment être saisie.

openssl x509 -req -in server-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out server-cert.pem

Refaisons la même chose pour la clé privée et le certificat du client :

openssl req -new -keyout client-key.pem -out client-req.pem -days 3600

openssl x509 -req -in client-req.pem -days 3600 -CA ca-cert.pem -CAkey ca-key.pem -set_serial 01 -out client-cert.pem

Nous avons maintenant 6 fichiers utiles : les trois certificats et les trois clés privées. Les deux fichiers de demande de certificat (xx-req.pem) ne nous servent plus et peuvent donc être effacés. La clé privée de la CA ne nous sera plus utile dans notre exemple, mais sera nécessaire pour créer de nouveaux certificats ! Il vaut donc mieux la garder :-)

Attention : Il semblerait que MySQL ne soit actuellement pas capable d'utiliser des clés privées protégées par une passphrase (et donc chiffrées). Voici donc comment enlever la passphrase d'une clé, en attendant un petit effort de MySQL AB...

openssl rsa -in server-key.pem -out server-key.pem
openssl rsa -in client-key.pem -out client-key.pem

Passons maintenant au démarrage du serveur et à l'établissement d'une connexion sécurisée par SSL.

Pour démarrer le serveur, nous spécifions la clé privée du serveur, le certificat du serveur et le certificat de la CA. Nous pouvons soit le faire en ligne de commande, soit directement dans le fichier de configuration.

# bin/mysqld_safe --user=mysql --ssl-key=/xxx/server-key.pem --ssl-ca=/yyy/ca-cert.pem --ssl-cert=/zzz/server-cert.pem &

Si tout s'est bien passé, le serveur est démarré. On peut vérifier que SSL est activé en se connectant au serveur et en regardant la variable have_openssl :

# mysql -u root -p

mysql> SHOW VARIABLES LIKE 'have_openssl';
+---------------+-------+

| Variable_name | Value |

+---------------+-------+

| have_openssl  | YES   |

+---------------+-------+

Tout fonctionne pour le mieux du côté du serveur. Créons maintenant un utilisateur qui devra obligatoirement établir des connexions SSL. Pour cela, nous spécifions l'option REQUIRE SSL lors du GRANT de privilèges.

mysql> CREATE DATABASE testssl;

mysql> GRANT CREATE ON testssl.* TO 'userSSL'@'%' IDENTIFIED BY 'ssl' REQUIRE SSL;

Pour se connecter, l'utilisateur userSSL devra spécifier le certificat de la CA et le mot de passe "ssl". Il pourra, mais sans obligation, spécifier sa clé privée et son certificat. Par l'option REQUIRE SSL, nous forçons l'utilisation de SSL, mais pas celle d'un certificat valide.

Si nous tentons de nous connecter sans le certificat de la CA, MySQL refuse l'accès :

# mysql -u userSSL -p -h adresse_ip_serveur
Enter password :

ERROR 1045 (28000) : Access denied for user 'userSSL'@'192.168.0.2' (using password: YES)

Mais si maintenant nous spécifions le certificat de la CA, la connexion s'établit et utilise SSL.

# mysql -u userSSL -p --ssl-ca=/xxx/ca-cert.pem -h adresse_ip_serveur
Enter password :

Welcome to the MySQL monitor. Commands end with ; or \g.

Pour vous assurer que le trafic réseau est chiffré entre votre client et votre serveur, vous pouvez lancer un sniffer (ex. : Wireshark) et voir les paquets qui passent. Vous remarquerez que toutes vos requêtes envoyées ainsi que leurs résultats sont illisibles, contrairement à une connexion normale.

Nous avons vu comment obliger SSL pour un utilisateur avec REQUIRE SSL. Si nous voulons maintenant obliger l'utilisation d'un certificat validé par la CA, nous spécifions l'option REQUIRE X509 :

mysql> GRANT CREATE ON testssl.* TO 'userSSLCertif'@'%' IDENTIFIED BY 'ssl' REQUIRE X509;

Pour se connecter (à distance), l'utilisateur userSSLCertif aura donc besoin de son mot de passe, du certificat de la CA, d'une clé privée et d'un certificat signé par la CA. S'il ne spécifie que son mot de passe et le certificat de la CA, MySQL lui refuse l'accès :

# mysql -u userSSLCertif -p --ssl-ca=/xxx/ca-cert.pem -h adresse_ip_serveur
Enter password :

ERROR 1045 (28000) : Access denied for user 'userSSLCertif'@'192.168.0.2' (using password: YES)

Par contre, avec les bons paramètres...

# mysql -u userSSLCertif -p --ssl-ca=/xxx/ca-cert.pem --ssl-cert=/xxx/client-cert.pem --ssl-key=/xxx/client-key.pem -h adresse_ip
Enter password :

Welcome to the MySQL monitor. Commands end with ; or \g.

Vous savez maintenant comment sécuriser vos connexions avec SSL. Si vous avez bien suivi ce chapitre, vous aurez remarqué qu'avec REQUIRE X509, il suffit d'avoir un certificat valide. Il n'est pas possible de contrôler le certificat qui est accepté pour un utilisateur précis... Espérons que cette fonctionnalité soit prochainement disponible pour enfin profiter d'une authentification forte à distance.

Il existe néanmoins des moyens pour renforcer le chiffrement et la sécurité lors de l'utilisation de SSL avec MySQL. Je vous conseille de consulter la documentation pour avoir de plus amples informations à ce sujet.

Dans les exemples précédents, nous étions notre propre CA. Des entreprises spécialisées permettent de créer et signer des certificats. La plupart de celles-ci sont payantes. Mais le site http://www.cacert.org propose ces services gratuitement.

X - Ne garder que les fonctionnalités nécessaires

Au fil des versions, MySQL s'enrichit de nouvelles fonctionnalités. En général, certaines estimées dangereuses en termes de sécurité sont désactivées par défaut. Mais cela ne veut pas dire que celles qui sont activées sont sans risque. Il est donc important de vérifier à chaque nouvelle version si les fonctionnalités activées vous sont utiles ou non ; et si elles ne le sont pas, de les désactiver pour ne pas prendre de risque.

Voici quelques exemples de fonctionnalités, certes très intéressantes, mais qu'il vaut mieux désactiver (ou laisser désactivées) si vous ne les utilisez pas :

- le programmateur d'événements (event scheduler), disponible à partir de MySQL 5.1, permet d'exécuter des opérations à certains moments spécifiques ou réguliers. Il est actuellement désactivé par défaut ;

- le moteur de stockage FEDERATED, qui sera apparemment activé par défaut à partir de MySQL 5.1, permet de lire et stocker des données dans une base distante ; Actuellement, pour la désactiver, il n'y a qu'un seul moyen : recompiler avec l'option --without-federated-storage-engine lors de l'opération de ./configure ;

- le support des liens symboliques permet de faciliter le stockage de tables sur plusieurs disques durs, mais peut s'avérer très dangereux ;

- la commande LOAD DATA LOCAL INFILE qui permet de remplir la base de données avec des fichiers situés chez le client. Elle est activée par défaut, mais nécessite le privilège FILE ;

- le moteur de stockage MERGE permet de manipuler plusieurs tables identiques MyISAM comme s'il ne s'agissait que d'une seule et même table. Le problème avec ce moteur de stockage peut se situer au niveau suivant : si un utilisateur a accès à une table t, cet utilisateur peut créer une table MERGE m qui accédera à t. Cependant, si un jour cet utilisateur perd l'accès à la table t, il pourra toujours y accéder en utilisant la table m.

Le fichier de configuration suivant permet de désactiver toutes ces fonctionnalités. Même si elles ne sont pas actuellement activées par défaut, je vous conseille de spécifier explicitement à MySQL de ne pas les utiliser. Qui sait... peut-être dans une prochaine version, certaines de ces fonctionnalités seront activées directement !

[mysqld]

event-scheduler=0
skip-symbolic-links
local-infile=0
skip-merge

À noter que les options event-scheduler et skip-merge ne sont disponibles qu'à partir de MySQL 5.1.

XI - Activer et analyser les fichiers de logs

Pour détecter toute activité suspecte, il est important d'activer les logs des requêtes et des erreurs du serveur, et de les analyser régulièrement.

Il existe différents logs dans MySQL. Celui qui nous intéresse le plus est le "General Query Log" qui enregistre toutes les connexions / déconnexions ainsi que toutes les requêtes exécutées. Ce log s'active avec l'option -l ou --log lors du démarrage du serveur MySQL. Il est même possible de spécifier où enregistrer ce fichier de log :

mysqld --log=/var/log/mysql_general.log
mysqld -l /var/log/mysql_general.log

Bien sûr, comme ce fichier comportera des informations pouvant compromettre la sécurité de votre base de données, il est indispensable de sécuriser l'accès à ce fichier (droits de lecture/écriture restreints).

De plus, ce genre de fichier grossit rapidement. Il faudra donc veiller régulièrement à ce qu'il ne remplisse pas votre disque dur !

Voici un exemple de fichier de log général :

Time Id Command Argument
061021 15:29:39 1 Connect root@localhost on
061021 15:29:44 1 Query SELECT DATABASE()
061021 15:30:03 1 Query CREATE baseEcommerce
061021 15:30:10 1 Query CREATE DATABASE baseEcommerce
061021 15:30:21 1 Query SELECT DATABASE()
1 Init DB baseEcommerce
061021 15:30:57 2 Connect utilisateur1@192.168.110.1 on
061021 15:31:13 1 Query CREATE TABLE article
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(30),
prix DECIMAL(10,2)
) ENGINE=MyISAM
061021 15:32:33 1 Query GRANT SELECT, INSERT, UPDATE, DELETE ON base Ecommerce.article TO 'manager_articles'@'localhost' IDENTIFIED BY 'ma567Pb*z'
061021 15:32:36 1 Quit

Pour chaque action, le fichier de log indique le jour et l'heure où elle a eu lieu, lors de quelle session elle a été réalisée (= colonne Id du fichier de log) et le type de la commande avec ses arguments.

Comme vous pouvez le remarquer, une personne qui aurait accès à ce fichier pourrait connaître la structure de tables ou encore mieux : obtenir le login et mot de passe d'un utilisateur.

XII - Spécifier des limites dans MySQL

MySQL propose différentes options permettant de rendre impossibles certaines opérations dans des conditions spécifiques, ou encore de limiter les ressources allouées aux utilisateurs.

L'option --max_user_connections permet de limiter le nombre de connexions actives possibles avec un même utilisateur. Cela s'applique à tous les utilisateurs.

L'option --max_connections permet de limiter le nombre de connexions totales, tous utilisateurs confondus.

Il est également possible d'appliquer des limites spécifiques à certains utilisateurs. Cela est réalisable grâce à la commande GRANT. L'exemple suivant limite un utilisateur à 10 SELECT et 20 UPDATE par heure, deux connexions maximum dans une heure et maximum une connexion simultanée :

mysql> GRANT USAGE ON *.* TO 'utilisateur'@'localhost' WITH MAX_QUERIES_PER_HOUR 10 MAX_UPDATES_PER_HOUR 20 MAX_CONNECTIONS_PER_HOUR 4 MAX_USER_CONNECTIONS 1;

Le privilège USAGE équivaut à "aucun droit spécifique". Tout utilisateur créé dans MySQL possède le privilège USAGE sur *.* et nous demandons ici de modifier ses options de base, sans rajouter d'autres privilèges. Bien sûr, il est possible de spécifier les limites de requêtes et connexions en même temps que l'attribution d'autres privilèges.

L'option --max_tmp_tables permet de limiter le nombre de tables temporaires ouvertes qu'un utilisateur peut avoir.

La variable de session wait_timeout permet de limiter le temps d'inactivité d'une connexion. Par exemple, pour limiter l'inactivité à 60 secondes, on peut exécuter la commande suivante après avoir établi la connexion :

mysql> SET wait_timeout=60;

Si le serveur ne reçoit aucune commande pendant une minute, il fermera donc la connexion. La plupart des clients se reconnectent automatiquement dès que l'utilisateur veut exécuter une nouvelle commande, comme on peut le voir dans l'exemple suivant avec le client mysql console :

mysql> SELECT USER();

ERROR 2006 (HY000) : MySQL server has gone away
No connection. Trying to reconnect...
Connection id: 3
Current database: *** NONE ***

Enfin, pour ne pas risquer d'exécuter une requête qui consommerait trop de ressources ou qui supprimerait ou modifierait beaucoup de données (ex. : si l'utilisateur oublie le WHERE dans une requête DELETE !!), il est possible de lancer le client console avec l'option --i-am-a-dummy (ou --safe-updates) qui limitera les SELECT à 1000 résultats, limitera les jointures à 1 million d'enregistrements analysés et qui empêchera tout UPDATE/DELETE qui ne possède pas de WHERE.

Ce comportement peut également être obtenu en modifiant des variables de session une fois la connexion établie :

SET SQL_SAFE_UPDATES=1;
SET SQL_SELECT_LIMIT=1000;
SET SQL_MAX_JOIN_SIZE=1000000;

XIII - Réflexions sur les mots de passe

Le choix des mots de passe est très important. Un mot de passe trop simple à "cracker" ouvrira une brèche dans la sécurité de votre système. C'est pour cette raison qu'il est indispensable d'utiliser des mots de passe sécurisés.

Tout d'abord, tous les comptes utilisateurs (système et MySQL) doivent avoir un mot de passe. Il n'est pas question de laisser un accès ouvert à quiconque, même avec des privilèges restreints. Ensuite, chacun de ces mots de passe devra être difficile à deviner ou cracker par des méthodes de dictionnaire ou de bruteforce.

Un bon mot de passe doit être relativement long (au moins 8 lettres), mélanger les minuscules et majuscules, contenir des chiffres et des caractères spéciaux, et surtout doit être différent du login ! Il faut également veiller à ce que les mots de passe n'aient aucun rapport avec les noms des utilisateurs ou encore leur date de naissance.

Le problème avec les mots de passe est de pouvoir s'en souvenir facilement, en évitant de les inscrire sur des Post-it ou encore dans des fichiers du disque dur. Un moyen simple d'obtenir un mot de passe facile à retenir, mais difficile à cracker, est de prendre un mot ou plusieurs mots relativement longs, par exemple "acces_restreint". Puis de remplacer certaines lettres par des chiffres, par exemple e par 3, i par 1, a par 4, o par 0… On obtient donc dans notre exemple : "4cc3s_r3str31nt". Nous pouvons ensuite mettre une lettre sur deux en majuscule : "4cC3s_R3sTr31Nt" et rajouter quelques caractères spéciaux : " *4cC3s_R3sTr31Nt# ". Et voilà un mot de passe plutôt bien sécurisé :-)

Ce choix judicieux de mots de passe sécurisés doit s'appliquer pour tout type d'accès : système, base de données, application…

Afin de limiter le risque de vol de compte, il est également préférable de modifier fréquemment les mots de passe des comptes utilisateurs. En ce qui concerne MySQL, aucune fonctionnalité permettant de limiter la validité d'un mot de passe n'est proposée, il faut donc utiliser une solution extérieure. Ce changement régulier des mots de passe aura tendance à énerver les utilisateurs... à vous de les convaincre de l'importance de cette action.

XIV - Quelques conseils d'utilisation en vrac

Terminons cet article par quelques conseils d'utilisation.

- Faites régulièrement des sauvegardes de votre base de données. Le risque zéro n'existe pas : une erreur humaine, une panne matérielle ou une faille exploitée par un pirate peut arriver. Pour réaliser vos sauvegardes, MySQL propose différents outils tels que mysqldump ou mysqlhotcopy. D'autres programmes comme PHPMyAdmin facilitent les sauvegardes et les restaurations.

- Prêtez une attention particulière aux failles d'injection SQL de vos applications. La règle d'or est de ne jamais faire confiance à l'utilisateur de l'application et de vérifier tous les paramètres entrés. Plusieurs méthodes existent pour se prémunir des injections SQL : vérification de validité des paramètres avec des expressions régulières, utilisation de prepared statements, utilisation des fonctions d'échappement de caractères spéciaux (ex. : mysql_real_escape_string)

- Utilisez des technologies complémentaires pour assurer la sécurité sur votre serveur et votre réseau : firewalls, systèmes de détection d'intrusions (IDS), mise en place d'une zone démilitarisée (DMZ)...

- Vérifiez les permissions des fichiers de configuration (ex. : my.cnf). Ceux-ci contiennent des informations qui pourraient intéresser certains pirates.

- Lorsque vous utilisez un client console (mysql, mysqldump, mysqladmin), évitez de saisir votre mot de passe en argument, qui serait dès lors visible à l'écran, mais également stocké en clair dans les historiques des commandes.

Ainsi, au lieu de faire :

mysql -u root -pMonMotDePasse

mysqladmin shutdown -u root --password=MonMotDePasse

mysql -u root -p

- Par défaut, un utilisateur peut lister toutes les bases de données sur lesquelles il possède des privilèges. Si vous voulez désactiver ce comportement, vous pouvez utiliser l'option --skip-show-database. Bien évidemment, si un utilisateur possède le privilège SHOW DATABASES, il pourra toujours voir l'ensemble des bases.

- Cet article ne traite pas de la disponibilité de l'information, qui représente pourtant un aspect crucial dans le domaine de la sécurité. En plus des sauvegardes régulières, vous pouvez mettre en place un cluster ou utiliser une réplication de données personnalisée ; qui sont tous deux des fonctionnalités grandement améliorées depuis MySQL 5.1.

Il est également recommandé d'utiliser des outils de monitoring afin de vous assurer de la bonne santé de votre serveur MySQL. Parmi ces outils, on peut citer le très connu Nagios couplé avec les plugins nagios-mysql-plugins.

- Si vous utilisez le client console (le binaire mysql) pour vos tâches d'administration, prenez conscience que ce programme enregistre l'historique de vos commandes dans ~/.mysql_history. Si vous voulez garder cette fonctionnalité, veillez à protéger l'accès à ce fichier.

Dans le cas contraire, il existe plusieurs possibilités pour la désactiver. Tout d'abord, fermez le client mysql et supprimez le fichier .mysql_history de votre home. Ensuite, recréez le fichier en tant que lien symbolique vers /dev/null :

ln -s /dev/null $HOME/.mysql_history

XV - Conclusion

Dans cet article, nous avons vu quelques méthodes pour améliorer la sécurité du serveur MySQL et des données qu'il gère. Le but de ce document n'est pas de donner un guide parfait de la sécurisation, mais plutôt de sensibiliser le lecteur et lui faire comprendre que chacune de ses actions, aussi insignifiantes qu'elles puissent paraître, peut avoir de sérieuses répercussions quant à la sécurité de son système.

J'espère que ce tutoriel vous aura été utile. Je reste bien sûr à votre disposition pour toute question (non technique, pour cela il y a le forum) ou remarque. Vous pouvez me contacter par email ou par message privé.

XVI - 17. Ressources complémentaires

Si vous désirez approfondir le sujet, voici quelques ressources intéressantes.

- Le chapitre de la documentation officielle de MySQL consacré à la sécurité :

http://dev.mysql.com/doc/refman/5.1/en/security.html

- Les chapitres de la documentation officielle de MySQL consacrés à GRANT, REVOKE, aux privilèges et à SSL :

http://dev.mysql.com/doc/refman/5.1/en/privilege-system.html
http://dev.mysql.com/doc/refman/5.1/en/grant.html
http://dev.mysql.com/doc/refman/5.1/en/revoke.html
http://dev.mysql.com/doc/refman/5.1/en/secure-connections.html

- Un script et des explications pour chrooter MySQL sous Ubuntu et Debian :

http://blog.blackdown.de/2006/12/30/chrooting-recent-mysql-versions-on-debian-and-ubuntu/

- Pluggable Authentication Support, un projet officiel qui vise à doter MySQL d'un système de plugins pour l'authentification (LDAP, Kerberos, authentification par le système d'exploitation...)

http://forge.mysql.com/wiki/PluggableAuthenticationSupport

- Conseils pour sécuriser MySQL sous Windows

http://dev.mysql.com/tech-resources/articles/securing_mysql_windows.html

18. Remerciements

Différentes personnes ont permis d'améliorer la qualité de cet article, je tiens à les remercier :

- Antoine Dinimant, auteur du livre Guide complet MySQL 5, éd. MicroApplication, pour sa relecture minutieuse et ses nombreux conseils.

- Maximilian, nyal et Swoög ; membres de developpez.com, pour leurs recommandations et aides.