I den første del af denne blog beskrev vi, hvordan ProxySQL kan bruges til at blokere indgående forespørgsler, der blev anset for farlige. Som du så i den blog, er det meget nemt at opnå dette. Dette er dog ikke en fuldstændig løsning. Det kan være, du skal designe en endnu tættere sikret opsætning - du vil måske blokere alle forespørgslerne og derefter tillade nogle udvalgte at passere igennem. Det er muligt at bruge ProxySQL til at opnå det. Lad os tage et kig på, hvordan det kan gøres.
Der er to måder at implementere hvidliste i ProxySQL. For det første ville den historiske være at oprette en opsamlingsregel, der blokerer alle forespørgsler. Det bør være den sidste forespørgselsregel i kæden. Et eksempel nedenfor:
Vi matcher hver streng og genererer en fejlmeddelelse. Dette er den eneste regel, der eksisterer på nuværende tidspunkt, den forhindrer enhver forespørgsel i at blive udført.
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.
Som du kan se, kan vi ikke køre nogen forespørgsler. For at vores applikation skal fungere, skal vi oprette forespørgselsregler for alle de forespørgsler, som vi vil tillade at udføre. Det kan gøres pr. forespørgsel, baseret på sammendraget eller mønsteret. Du kan også tillade trafik baseret på de andre faktorer:brugernavn, klientvært, skema. Lad os tillade SELECTs til en af tabellerne:
Nu kan vi udføre forespørgsler på denne tabel, men ikke på nogen anden:
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.
Problemet med denne tilgang er, at den ikke håndteres effektivt i ProxySQL, derfor kommer ProxySQL 2.0.9 med en ny mekanisme for firewalling, som inkluderer ny algoritme, fokuseret på denne særlige brugssag og som sådan mere effektiv. Lad os se, hvordan vi kan bruge det.
Først skal vi installere ProxySQL 2.0.9. Du kan downloade pakker manuelt fra https://github.com/sysown/proxysql/releases/tag/v2.0.9, eller du kan konfigurere ProxySQL-lageret.
Når dette er gjort, kan vi begynde at undersøge det og prøve at konfigurere det til at bruge SQL firewall.
Processen i sig selv er ret nem. Først og fremmest skal du tilføje en bruger til tabellen mysql_firewall_whitelist_users. Den indeholder alle de brugere, som firewall skal være aktiveret for.
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)
I forespørgslen ovenfor føjede vi 'sbtest'-brugeren til listen over brugere, som skulle have firewall aktiveret. Det er muligt at se, at kun forbindelser fra en given vært testes mod firewall-reglerne. Du kan også have tre tilstande:'OFF', når firewall ikke bruges, 'DETECTING', hvor forkerte forespørgsler logges, men ikke blokeres, og 'PROTECTING', hvor ikke tilladte forespørgsler ikke vil blive udført.
Lad os aktivere vores firewall:
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)
ProxySQL firewall baserer sig på sammendraget af forespørgslerne, det tillader ikke, at regulære udtryk bruges. Den bedste måde at indsamle data om, hvilke forespørgsler der skal tillades, er at bruge tabellen stats.stats_mysql_query_digest, hvor du kan indsamle forespørgsler og deres sammendrag. Oven i det kommer ProxySQL 2.0.9 med en ny tabel:history_mysql_query_digest, som er en vedvarende udvidelse til den tidligere nævnte in-memory tabel. Du kan konfigurere ProxySQL til at gemme data på disk fra tid til anden:
mysql> SET admin-stats_mysql_query_digest_to_disk=30;
Query OK, 1 row affected (0.00 sec)
Hvert 30. sekund vil data om forespørgsler blive gemt på disken. Lad os se, hvordan det går. Vi udfører et par forespørgsler og tjekker derefter deres sammendrag:
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)
Når vi indstiller firewallen til 'OPSÆTNING'-tilstand, vil vi også se indgange i loggen:
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]
Nu, hvis vi vil begynde at blokere forespørgsler, bør vi opdatere vores bruger og indstille tilstanden til 'BESKYTTELSE'. Dette vil blokere al trafik, så lad os starte med at hvidliste forespørgsler ovenfor. Så aktiverer vi 'BESKYTTELSE'-tilstanden:
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)
Det er det. Nu kan vi udføre hvidlistede forespørgsler:
mysql> SELECT id, k FROM sbtest1 LIMIT 2;
+------+------+
| id | k |
+------+------+
| 7615 | 1942 |
| 3355 | 2310 |
+------+------+
2 rows in set (0.00 sec)
Men vi kan ikke udføre ikke-hvidlistede:
mysql> SELECT id, k FROM sbtest3 LIMIT 2;
ERROR 1148 (42000): Firewall blocked this query
ProxySQL 2.0.9 kommer med endnu en interessant sikkerhedsfunktion. Den har indlejret libsqlinjection, og du kan aktivere detektering af mulige SQL-injektioner. Detektion er baseret på algoritmerne fra libsqlinjection. Denne funktion kan aktiveres ved at køre:
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)
Det fungerer med firewallen på følgende måde:
- Hvis firewallen er aktiveret, og brugeren er i PROTECTING-tilstand, bruges SQL-injektionsdetektion ikke, da kun eksplicit hvidlistede forespørgsler kan passere.
- Hvis firewallen er aktiveret, og brugeren er i DETECTING-tilstand, testes hvidlistede forespørgsler ikke for SQL-injektion, alle andre vil blive testet.
- Hvis firewallen er aktiveret, og brugeren er i 'OFF'-tilstand, antages alle forespørgsler at være hvidlistede, og ingen vil blive testet for SQL-injektion.
- Hvis firewallen er deaktiveret, vil alle forespørgsler blive testet for SQL-inteektion.
Grundlæggende bruges det kun, hvis firewallen er deaktiveret eller for brugere i 'OPSÆTNING'-tilstand. SQL-injektionsdetektion kommer desværre med en hel del falske positiver. Du kan bruge tabellen mysql_firewall_whitelist_sqli_fingerprints til at hvidliste fingeraftryk for forespørgsler, der blev opdaget forkert. Lad os se, hvordan det virker. Lad os først deaktivere firewall:
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)
Så lad os køre nogle forespørgsler.
mysql> SELECT id, k FROM sbtest2 LIMIT 2;
ERROR 2013 (HY000): Lost connection to MySQL server during query
Der er faktisk falske positiver. I loggen kunne vi finde:
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, lad os tilføje dette fingeraftryk til hvidlistetabellen:
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)
Nu kan vi endelig udføre denne forespørgsel:
mysql> SELECT id, k FROM sbtest2 LIMIT 2;
+------+------+
| id | k |
+------+------+
| 84 | 2456 |
| 6006 | 2588 |
+------+------+
2 rows in set (0.01 sec)
Vi forsøgte at køre sysbench-arbejdsbelastning, dette resulterede i yderligere to fingeraftryk tilføjet til hvidlistetabellen:
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
Vi ville se, om denne automatiske SQL-injektion kan beskytte os mod vores gode ven, 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)
Desværre ikke rigtig. Husk at denne funktion er baseret på automatiske retsmedicinske algoritmer, den er langt fra perfekt. Det kan komme som et ekstra lag af forsvar, men det vil aldrig være i stand til at erstatte korrekt vedligeholdt firewall oprettet af en person, der kender applikationen og dens forespørgsler.
Vi håber, at du efter at have læst denne korte serie i to dele har en bedre forståelse af, hvordan du kan beskytte din database mod SQL-injektion og ondsindede forsøg (eller blot brugerfejl) ved hjælp af ProxySQL. Hvis du har flere ideer, hører vi meget gerne fra dig i kommentarerne.