Outils pour utilisateurs

Outils du site


bsd:bsda

Securisation de Mysqld

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


Après l'installation il ne faut pas lancer le daemon mysqld avec les privilèges de l'utilisateur système root.
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.

Pourquoi certain décident d'utiliser de lancer le daemon mysqld avec les privilèges de l'utilisateur root ?

  • par méconnaissance des droits et de l'administration du système et par ce faite utiliser une méthode plus rapide car le serveur MySQL doit lire et écrire dans certains fichiers auxquels l'utilisateur régulier n'a pas accès
  • en lançant le daemon mysqld avec les privilèges root, on a l'assurance que MySQL aura les droits nécessaires


Que peut il se passer dans ce mode de fonctionnement de super-utilisateur?

  • le processus daemon mysqld à tous les droits sur l'entierté du système !
  • si il y a une faille de type buffer overflow dans MySQL, un pirate pourra prendre aisément le contrôle du processus et vu que le processus daemon disposera de tous les privilèges root, le pirate deviendra le maître absolu de la machine. Il pourra donc faire tout ce qu'il veut, dites adieu à votre machine et à son contenu mais aussi à votre réseaux local !


Il faut donc:

  1. créer un utilisateur _mysql dont la seule fonction sera de lancer le daemon mysqld et empêcher cet utilisateur _mysql de se logger lui donnant accès à un bash (un accès à la cmd)
  2. que cet utilisateur ait le moins de droits système possible
  3. que cet utilisateur n'ait accès qu'aux fichiers propres à MySQL
  4. mettre cet utilisateur _mysql dans un groupe _mysql
groupadd _mysql
useradd -g _mysql _mysql
useradd -d /nonexistent -s /sbin/nologin -g _mysql _mysql

Sur OpenBSD lors de l'installation de mariadb-server qui est le serveur MySQL, l'utilisateur _mysql et le groupe _mysql sont créés. L'utilisateur créé ne permet pas de se logger!

Il suffit de vérifier le fichiers messages

cat /var/log/messages | grep _mysql
new group added: name=_mysql, gid=502
new user added: name=_mysql, uid=502, gid=502, home=/nonexistent, shell=/sbin/nologin

On peut vérifier les utilisateurs et groupes créés avec les commandes suivantes:

cat /etc/passwd
_mysql: *:502:502:MySQL Account:/nonexistent:/sbin/nologin
cat /etc/group
_mysql: *:502:

Vérification des droits d’accès aux fichiers

ls -l /var
drwx------   5 _mysql  _mysql    1024 Jul 21 16:39 mysql
700

On spécifie que l'utilisateur _mysql sera le seul propriétaire du daemon mysqld

nano /etc/my.cnf
[mysqld]
user = _mysql
ou
cd /var/mysql
bin/mysqld_safe --user=_mysql &

On reboot le serveur

rcctl restart mysqld

On vérifie que que le serveur MySQL tourne avec les droits limités

ps -aux | grep '_mysql'
_mysql   35202  0.0  2.3 326788 48952


Sécurisation de l'utilisateur root


Lors de l'installation, MySQl va créer un utilisateur nommé root qui possède tous les privilèges sur la base de données.Cet utilisateur n'a aucun mot de passe par défaut, ce qui laisse une fois de plus la possibilité et la porte grande ouverte à un pirate !

Remédier au nom d'utilisateur et passe par défaut

On va donc ajouter un mot de passe mais aussi changer le nom d'utilisateur root.
Cela rendra encore plus difficile toute tentative de s'approprier les droits d'administration sur le serveur SQL. Il faudra donc que le pirate trouve non seulement le mot de passe mais également le nom de l’utilisateur !
Méthode 1

mysql -u root -p
UPDATE mysql.user SET User = 'votre_nouveau_nom_utilisateur', Password = PASSWORD('votre_nouveau_mot_de_passe') WHERE User='root';
FLUSH PRIVILEGES;

FLUSH PRIVILEGES; (permet de recharger les privilèges des utilisateurs sans avoir à redémarrer le serveur MySQL)

Méthode 2

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

ou

SET PASSWORD FOR root@localhost = PASSWORD('nouveau_mot_de_passe');
RENAME USER root@localhost TO nouveau_nom_utilisateur@localhost;

Vérifier la liste des utilisateurs

SELECT Host,User FROM mysql.user;

A ce stade ci le compte root sous MySQL n'existe plus, il faudra utiliser le nom et mot de passe indiqués dans la requête car on a un nouvel administrateur de la base de données.
Attention le script mysql_secure_installation ne fonctionnera plus !


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 :

mysql -u  votre_nouveau_nom_utilisateur -p
DELETE FROM mysql.user WHERE User='';
FLUSH PRIVILEGES;

Si on pense avoir créé un utilisateur sans mot de passe :

DELETE FROM mysql.user WHERE Password='';
FLUSH PRIVILEGES;


Détruire la bas de donnée test

Par défaut MySQL crée une data base de test. Cette data base est utile lors de la mise en place de la structure de nos data base mais elle doit être supprimée au plus vite car elle est accessible par défaut à tout utilisateur, ce qui est extrèmement dangerux en matire de sécurité. On eut aussi changer les privilèges d'accès au lieu de la supprimer.

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


Utiliser efficacement les valeurs d'hôte


La particularité de MySQL est que chaque nom d'utilisateur est associé à un hôte (host). Celui-ci permet donc 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é sous la forme nom_utilisateur@hote.
Il est donc important de profiter de cette fonctionnalité pour renforcer la sécurité de l'accès à notre data base, mais pour éviter toute erreur dangereuse, il faut connaître le comportement de MySQL pour la gestion de ces hôtes !

Pour s'authentifier à un serveur MySQL, il faut fournir:

  • un nom d'utilisateur correct
  • le bon mot de passe
  • se connecter à partir de l'hôte lié à l'utilisateur

Il faut donc que le triplet (username, host, password) existe dans la data base.

La valeur du champs hôte peut être une adresse IP ou un nom d'hôte.
S'il s'agit d'un nom d'hôte, celui-ci devra être traduit en adresse IP soit par le serveur DNS ou soit par le fichier /etc/hosts.

Attention que si le serveur DNS sur lequel on dépend pour traduire les noms d'hôte en adresse IP est corrompus (spoofé), la traduction de noms d'hôte en adresse IP sera faussée et un pirate pourra alors assigner n'importe quelle adresse IP à un nom d'hôte.

Il est donc préférable d'éviter au maximum les noms d'hôtes dans MySQL, sous forme de caractères, plutôt que les adresses IP complètes.

Pour créer un utilisateur qui pourra se connecter à partir de l'adresse IP 192.168.1.50 (LAN)

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

Pour les connexions locales (à partir de l'hôte même) on n'a pas besoin de spécifier des adresses IP complètes et on peut 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ées localement sur le système hôte. 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 on peut utiliser de manière sécurisée des noms d'hôtes dans MySQL et donc se passer de serveur DNS.

Pour créer un utilisateur qui pourra se connecter à partir de l’entièreté du réseaux 192.168.1.0 (LAN)

CREATE USER 'toto'@'192.168.1.0/255.255.255.0' IDENTIFIED BY 'motdepasse';


Les jokers (wildcards)

MySQL permet également d'utiliser des jokers (wildcards) dans l'expression des hôtes.
Comme pour la close LIKE en SQL, un joker est représenté par les caractères % ou _.

  • le joker _ signifie “n'importe quel caractère”
  • le joker % signifie “n'importe quelle suite de caractères”

Le joker % utilisé seul dans un hôte permet de représenter n’importe quelle adresse IP 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 du réseau local sont dynamiques (ex. : DHCP), on pourra 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.

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

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

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

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

Pour supprimer l'utilisateur

delete from mysql.user where user='toto@192.168.1.0/255.255.255.0' and host = '%';


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, 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 '...';


Exercice:
Je veux que utilisateur :

  • zozo puisse se connecter uniquement à partir de la machine localhost
  • aiko puisse se connecter uniquement que à partir de 192.168.1.2
  • toto puisse se connecter à partir de toute machine du réseau 192.168.1.0
  • spi puisse se connecter à partir de toute machine du réseau 192.168.1.0 mais avec une syntaxe différente
CREATE USER 'zozo'@'localhost' IDENTIFIED BY 'a';
CREATE USER 'aiko'@192.168.1.202' IDENTIFIED BY 'a';
CREATE USER 'toto'@'192.168.1.0/255.255.255.0' IDENTIFIED BY 'a';
CREATE USER 'spi'@'192.168.1.%' IDENTIFIED BY 'a';
SELECT Host,User FROM mysql.user;
HostUser
localhostzozo
192.168.1.202aiko
192.168.1.0/255.255.255.0toto
192.168.1.%spi

8-O

Test de connexion

mysql -u zozo -p

mysql  -h 192.168.1.206 -u aiko -p

mysql  -h 192.168.1.206 -u toto -p

mysql  -h 192.168.1.206 -u spi -p

Bon reste plus qu'a mettre certains droits pour que mes utilisateurs puissent faire quelque chose ;-)

Codes erreurs

ERROR 2003 (HY000): Can't connect to MySQL server on '192.168.1.206' (111 “Connection refused”)
bind-address = 127.0.0.1

nano /etc/my.cnf
[mysqld]
#bind-address   = 127.0.0.1
bind-address   = 192.168.1.206

ERROR 1045 (28000): Access denied for user 'aiko'@'ok.home.lan' (using password: YES)

ERROR 1130 (HY000): Host 'ok.home.lan' is not allowed to connect to this MariaDB server
L'utilisateur n’existe pas il faut le créer


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.

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;
  • skip-external-locking : indique au serveur d’ignorer les requêtes extérieures
  • bind-address = 127.0.0.1 : indique au serveur sur quelle adresse il faut écouter les requêtes.

Si on veut accéder à un serveur MYSQL sur le réseau

  • bind-address = 192.168.1.206 (adresse IP du serveur sur le LAN)

Par défaut, il s’agit de 127.0.0.1. Si on veut autoriser le réseau local, n’indiquez pas une adresse réseau mais bien l’adresse du serveur sur le réseau. D’où l’intérêt d’avoir des réglages IP fixes ou des baux permanents attribués par le DHCP. Dans le cas contraire, je vous promet des heures de recherche infructueuses avant de revenir dessus.


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


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.

Creation de trois utilisateurs

1. 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);

2. 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 utiliser INSERT pour insérer dans les deux tables
  • le troisième pourra réaliser des SELECT, INSERT, UPDATE et DELETE dans toute la base.

3. On créé le premier utilisateur et on lui donne les privilèges ⇒ utilisateur1:

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.

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

mysql -h 192.168.1.206 -u utilisateur1 -p

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.

4. On créé le deuxième utilisateur et on lui donne les privilèges ⇒ utilisateur2:

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

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

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

mysql -h 192.168.1.206 -u utilisateur2 -p

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)&#160;: DELETE command denied to user 'utilisateur2'@'localhost' for table 'testgrant2'
mysql> INSERT INTO mabase.testGrant1 VALUES (1, 'allo&#160;?');

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.

5. On créé le troisième utilisateur et on lui donne les privilèges pour les opérations courantes ⇒ utilisateur3:

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

Dans la pratique

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 ;
  • l'utilisateur n° 2 soit utilisé par une application dont le but est de remplir les tables, sans avoir besoin de les lire ;
  • 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 SELECTINTO 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 SELECTINTO 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 !




How To BSD

bsd/bsda.txt · Dernière modification : de 127.0.0.1