MariaDB
 sql >> Base de données >  >> RDS >> MariaDB

Comment protéger votre base de données MySQL ou MariaDB contre l'injection SQL :deuxième partie

Dans la première partie de ce blog, nous avons décrit comment ProxySQL peut être utilisé pour bloquer les requêtes entrantes jugées dangereuses. Comme vous l'avez vu dans ce blog, y parvenir est très facile. Ce n'est pas une solution complète, cependant. Vous devrez peut-être concevoir une configuration encore plus sécurisée - vous voudrez peut-être bloquer toutes les requêtes, puis autoriser uniquement certaines requêtes à passer. Il est possible d'utiliser ProxySQL pour y parvenir. Voyons comment cela peut être fait.

Il existe deux façons d'implémenter une liste blanche dans ProxySQL. La première, historique, serait de créer une règle fourre-tout qui bloquera toutes les requêtes. Il doit s'agir de la dernière règle de requête de la chaîne. Un exemple ci-dessous :

Nous faisons correspondre chaque chaîne et générons un message d'erreur. C'est la seule règle existante à ce moment, elle empêche toute requête d'être exécutée.

mysql> USE sbtest;

Database changed

mysql> SELECT * FROM sbtest1 LIMIT 10;

ERROR 1148 (42000): This query is not on the whitelist, you have to create a query rule before you'll be able to execute it.

mysql> SHOW TABLES FROM sbtest;

ERROR 1148 (42000): This query is not on the whitelist, you have to create a query rule before you'll be able to execute it.

mysql> SELECT 1;

ERROR 1148 (42000): This query is not on the whitelist, you have to create a query rule before you'll be able to execute it.

Comme vous pouvez le voir, nous ne pouvons exécuter aucune requête. Pour que notre application fonctionne, nous devrions créer des règles de requête pour toutes les requêtes que nous voulons autoriser à exécuter. Cela peut être fait par requête, en fonction du résumé ou du modèle. Vous pouvez également autoriser le trafic en fonction des autres facteurs :nom d'utilisateur, hôte client, schéma. Autorisons les SELECT à l'une des tables :

Nous pouvons désormais exécuter des requêtes sur cette table, mais pas sur une autre :

mysql> SELECT id, k FROM sbtest1 LIMIT 2;

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

| id   | k |

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

| 7615 | 1942 |

| 3355 | 2310 |

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

2 rows in set (0.01 sec)

mysql> SELECT id, k FROM sbtest2 LIMIT 2;

ERROR 1148 (42000): This query is not on the whitelist, you have to create a query rule before you'll be able to execute it.

Le problème avec cette approche est qu'elle n'est pas gérée efficacement dans ProxySQL, donc dans ProxySQL 2.0.9 est livré avec un nouveau mécanisme de pare-feu qui inclut un nouvel algorithme, axé sur ce cas d'utilisation particulier et en tant que tel plus efficace. Voyons comment nous pouvons l'utiliser.

Tout d'abord, nous devons installer ProxySQL 2.0.9. Vous pouvez télécharger les packages manuellement à partir de https://github.com/sysown/proxysql/releases/tag/v2.0.9 ou vous pouvez configurer le référentiel ProxySQL.

Une fois cela fait, nous pouvons commencer à l'examiner et essayer de le configurer pour utiliser le pare-feu SQL.

Le processus lui-même est assez simple. Tout d'abord, vous devez ajouter un utilisateur à la table mysql_firewall_whitelist_users. Il contient tous les utilisateurs pour lesquels le pare-feu doit être activé.

mysql> INSERT INTO mysql_firewall_whitelist_users (username, client_address, mode, comment) VALUES ('sbtest', '', 'DETECTING', '');

Query OK, 1 row affected (0.00 sec)

mysql> LOAD MYSQL FIREWALL TO RUNTIME;

Query OK, 0 rows affected (0.00 sec)

Dans la requête ci-dessus, nous avons ajouté l'utilisateur "sbtest" à la liste des utilisateurs pour lesquels le pare-feu doit être activé. Il est possible de dire que seules les connexions d'un hôte donné sont testées par rapport aux règles du pare-feu. Vous pouvez également avoir trois modes :"OFF", lorsque le pare-feu n'est pas utilisé, "DETECTING", où les requêtes incorrectes sont enregistrées mais pas bloquées et "PROTECTING", où les requêtes non autorisées ne seront pas exécutées.

Activons notre pare-feu :

mysql> SET mysql-firewall_whitelist_enabled=1;

Query OK, 1 row affected (0.00 sec)

mysql> LOAD MYSQL VARIABLES TO RUNTIME;

Query OK, 0 rows affected (0.00 sec)

Le pare-feu ProxySQL se base sur le résumé des requêtes, il ne permet pas l'utilisation d'expressions régulières. La meilleure façon de collecter des données sur les requêtes autorisées est d'utiliser la table stats.stats_mysql_query_digest, où vous pouvez collecter les requêtes et leurs résumés. En plus de cela, ProxySQL 2.0.9 est livré avec une nouvelle table :history_mysql_query_digest, qui est une extension persistante de la table en mémoire mentionnée précédemment. Vous pouvez configurer ProxySQL pour stocker des données sur disque de temps en temps :

mysql> SET admin-stats_mysql_query_digest_to_disk=30;

Query OK, 1 row affected (0.00 sec)

Toutes les 30 secondes, les données relatives aux requêtes seront stockées sur le disque. Voyons comment ça se passe. Nous allons exécuter quelques requêtes, puis vérifier leurs résumés :

mysql> SELECT schemaname, username, digest, digest_text FROM history_mysql_query_digest;

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

| schemaname | username | digest             | digest_text |

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

| sbtest     | sbtest | 0x76B6029DCBA02DCA | SELECT id, k FROM sbtest1 LIMIT ? |

| sbtest     | sbtest | 0x1C46AE529DD5A40E | SELECT ?                          |

| sbtest     | sbtest | 0xB9697893C9DF0E42 | SELECT id, k FROM sbtest2 LIMIT ? |

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

3 rows in set (0.00 sec)

Alors que nous définissons le pare-feu sur le mode "DÉTECTION", nous verrons également des entrées dans le journal :

2020-02-14 09:52:12 Query_Processor.cpp:2071:process_mysql_query(): [WARNING] Firewall detected unknown query with digest 0xB9697893C9DF0E42 from user [email protected]

2020-02-14 09:52:17 Query_Processor.cpp:2071:process_mysql_query(): [WARNING] Firewall detected unknown query with digest 0x76B6029DCBA02DCA from user [email protected]

2020-02-14 09:52:20 Query_Processor.cpp:2071:process_mysql_query(): [WARNING] Firewall detected unknown query with digest 0x1C46AE529DD5A40E from user [email protected]

Maintenant, si nous voulons commencer à bloquer les requêtes, nous devons mettre à jour notre utilisateur et définir le mode sur "PROTÉGER". Cela bloquera tout le trafic, alors commençons par ajouter les requêtes à la liste blanche ci-dessus. Ensuite, nous activerons le mode "PROTÉGER" :

mysql> INSERT INTO mysql_firewall_whitelist_rules (active, username, client_address, schemaname, digest, comment) VALUES (1, 'sbtest', '', 'sbtest', '0x76B6029DCBA02DCA', ''), (1, 'sbtest', '', 'sbtest', '0xB9697893C9DF0E42', ''), (1, 'sbtest', '', 'sbtest', '0x1C46AE529DD5A40E', '');

Query OK, 3 rows affected (0.00 sec)

mysql> UPDATE mysql_firewall_whitelist_users SET mode='PROTECTING' WHERE username='sbtest' AND client_address='';

Query OK, 1 row affected (0.00 sec)

mysql> LOAD MYSQL FIREWALL TO RUNTIME;

Query OK, 0 rows affected (0.00 sec)

mysql> SAVE MYSQL FIREWALL TO DISK;

Query OK, 0 rows affected (0.08 sec)

C'est tout. Nous pouvons maintenant exécuter des requêtes en liste blanche :

mysql> SELECT id, k FROM sbtest1 LIMIT 2;

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

| id   | k |

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

| 7615 | 1942 |

| 3355 | 2310 |

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

2 rows in set (0.00 sec)

Mais nous ne pouvons pas exécuter ceux qui ne figurent pas sur la liste blanche :

mysql> SELECT id, k FROM sbtest3 LIMIT 2;

ERROR 1148 (42000): Firewall blocked this query

ProxySQL 2.0.9 est livré avec une autre fonctionnalité de sécurité intéressante. Il a intégré libsqlinjection et vous pouvez activer la détection d'éventuelles injections SQL. La détection est basée sur les algorithmes de libsqlinjection. Cette fonctionnalité peut être activée en exécutant :

mysql> SET mysql-automatic_detect_sqli=1;

Query OK, 1 row affected (0.00 sec)

mysql> LOAD MYSQL VARIABLES TO RUNTIME;

Query OK, 0 rows affected (0.00 sec)

Cela fonctionne avec le pare-feu de la manière suivante :

  • Si le pare-feu est activé et que l'utilisateur est en mode PROTECTING, la détection d'injection SQL n'est pas utilisée car seules les requêtes explicitement en liste blanche peuvent passer.
  • Si le pare-feu est activé et que l'utilisateur est en mode DETECTION, les requêtes en liste blanche ne sont pas testées pour l'injection SQL, toutes les autres seront testées.
  • Si le pare-feu est activé et que l'utilisateur est en mode "OFF", toutes les requêtes sont supposées être sur liste blanche et aucune ne sera testée pour l'injection SQL.
  • Si le pare-feu est désactivé, toutes les requêtes seront testées pour l'inspection SQL.

Fondamentalement, il n'est utilisé que si le pare-feu est désactivé ou pour les utilisateurs en mode "DÉTECTION". La détection d'injection SQL, malheureusement, s'accompagne d'un grand nombre de faux positifs. Vous pouvez utiliser la table mysql_firewall_whitelist_sqli_fingerprints pour mettre en liste blanche les empreintes digitales pour les requêtes qui ont été détectées de manière incorrecte. Voyons voir comment ça fonctionne. Désactivons d'abord le pare-feu :

mysql> set mysql-firewall_whitelist_enabled=0;

Query OK, 1 row affected (0.00 sec)

mysql> LOAD MYSQL VARIABLES TO RUNTIME;

Query OK, 0 rows affected (0.00 sec)

Ensuite, lançons quelques requêtes.

mysql> SELECT id, k FROM sbtest2 LIMIT 2;

ERROR 2013 (HY000): Lost connection to MySQL server during query

En effet, il y a des faux positifs. Dans le journal, nous pourrions trouver :

2020-02-14 10:11:19 MySQL_Session.cpp:3393:handler(): [ERROR] SQLinjection detected with fingerprint of 'EnknB' from client [email protected] . Query listed below:

SELECT id, k FROM sbtest2 LIMIT 2

Ok, ajoutons cette empreinte au tableau de la liste blanche :

mysql> INSERT INTO mysql_firewall_whitelist_sqli_fingerprints VALUES (1, 'EnknB');

Query OK, 1 row affected (0.00 sec)

mysql> LOAD MYSQL FIREWALL TO RUNTIME;

Query OK, 0 rows affected (0.00 sec)

Nous pouvons enfin exécuter cette requête :

mysql> SELECT id, k FROM sbtest2 LIMIT 2;

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

| id   | k |

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

|   84 | 2456 |

| 6006 | 2588 |

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

2 rows in set (0.01 sec)

Nous avons essayé d'exécuter la charge de travail sysbench, cela a entraîné l'ajout de deux autres empreintes digitales au tableau de la liste blanche :

2020-02-14 10:15:55 MySQL_Session.cpp:3393:handler(): [ERROR] SQLinjection detected with fingerprint of 'Enknk' from client [email protected] . Query listed below:

SELECT c FROM sbtest21 WHERE id=49474

2020-02-14 10:16:02 MySQL_Session.cpp:3393:handler(): [ERROR] SQLinjection detected with fingerprint of 'Ef(n)' from client [email protected] . Query listed below:

SELECT SUM(k) FROM sbtest32 WHERE id BETWEEN 50053 AND 50152

Nous voulions voir si cette injection SQL automatisée pouvait nous protéger contre notre bon ami Booby Tables.

mysql> CREATE TABLE school.students (id INT, name VARCHAR(40));

Query OK, 0 rows affected (0.07 sec)

mysql> INSERT INTO school.students VALUES (1, 'Robert');DROP TABLE students;--

Query OK, 1 row affected (0.01 sec)

Query OK, 0 rows affected (0.04 sec)

mysql> SHOW TABLES FROM school;

Empty set (0.01 sec)

Malheureusement, pas vraiment. Veuillez garder à l'esprit que cette fonctionnalité est basée sur des algorithmes médico-légaux automatisés, elle est loin d'être parfaite. Il peut constituer une couche de défense supplémentaire, mais il ne pourra jamais remplacer un pare-feu correctement entretenu créé par quelqu'un qui connaît l'application et ses requêtes.

Nous espérons qu'après avoir lu cette courte série en deux parties, vous comprenez mieux comment vous pouvez protéger votre base de données contre les injections SQL et les tentatives malveillantes (ou tout simplement les erreurs de l'utilisateur) à l'aide de ProxySQL. Si vous avez d'autres idées, nous serions ravis de vous entendre dans les commentaires.