В първата част на този блог описахме как ProxySQL може да се използва за блокиране на входящи заявки, които бяха счетени за опасни. Както видяхте в този блог, постигането на това е много лесно. Това обаче не е пълно решение. Може да се наложи да проектирате още по-строго защитена настройка - може да искате да блокирате всички заявки и след това да позволите само на някои избрани да преминат. Възможно е да се използва ProxySQL, за да се постигне това. Нека да разгледаме как може да се направи.
Има два начина за внедряване на белия списък в ProxySQL. Първо, историческото, би било да се създаде всеобхватно правило, което ще блокира всички заявки. Това трябва да е последното правило за заявка във веригата. Пример по-долу:
Ние съпоставяме всеки низ и генерираме съобщение за грешка. Това е единственото правило, съществуващо в момента, то предотвратява изпълнението на всяка заявка.
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.
Както виждате, не можем да изпълняваме никакви заявки. За да работи нашето приложение, ще трябва да създадем правила за заявки за всички заявки, които искаме да позволим да се изпълняват. Може да се направи на заявка, въз основа на обобщения или модел. Можете също да разрешите трафик въз основа на други фактори:потребителско име, клиентски хост, схема. Нека разрешим SELECT към една от таблиците:
Сега можем да изпълняваме заявки на тази таблица, но не и на друга:
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.
Проблемът с този подход е, че той не се обработва ефективно в ProxySQL, следователно в ProxySQL 2.0.9 идва с нов механизъм за защитна стена, който включва нов алгоритъм, фокусиран върху този конкретен случай на употреба и като такъв повече ефективен. Нека видим как можем да го използваме.
Първо, трябва да инсталираме ProxySQL 2.0.9. Можете да изтеглите пакети ръчно от https://github.com/sysown/proxysql/releases/tag/v2.0.9 или можете да настроите ProxySQL хранилището.
След като това стане, можем да започнем да го разглеждаме и да се опитаме да го конфигурираме да използва SQL защитна стена.
Самият процес е доста лесен. На първо място, трябва да добавите потребител към таблицата mysql_firewall_whitelist_users. Той съдържа всички потребители, за които защитната стена трябва да бъде активирана.
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)
В заявката по-горе добавихме потребител „sbtest“ към списъка с потребители, които трябва да имат активирана защитна стена. Възможно е да се каже, че само връзките от даден хост се тестват спрямо правилата на защитната стена. Можете също да имате три режима:„ИЗКЛЮЧЕН“, когато не се използва защитна стена, „ОТКРИВАНЕ“, където неправилните заявки се регистрират, но не се блокират, и „ЗАЩИТА“, когато неразрешените заявки няма да се изпълняват.
Нека активираме нашата защитна стена:
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 се основава на обобщението на заявките, не позволява използването на регулярни изрази. Най-добрият начин за събиране на данни за това кои заявки трябва да бъдат разрешени е да използвате таблица stats.stats_mysql_query_digest, където можете да събирате заявки и техните обобщения. Освен това, ProxySQL 2.0.9 идва с нова таблица:history_mysql_query_digest, която е постоянно разширение към споменатата по-рано таблица в паметта. Можете да конфигурирате ProxySQL да съхранява данни на диск от време на време:
mysql> SET admin-stats_mysql_query_digest_to_disk=30;
Query OK, 1 row affected (0.00 sec)
На всеки 30 секунди данните за заявките ще се съхраняват на диска. Да видим как върви. Ще изпълним няколко заявки и след това ще проверим техните обобщения:
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)
Тъй като настроим защитната стена в режим „ДЕТЕКТИРАНЕ“, ще видим и записи в регистрационния файл:
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]
Сега, ако искаме да започнем да блокираме заявки, трябва да актуализираме нашия потребител и да зададем режима на „ЗАЩИТА“. Това ще блокира целия трафик, така че нека започнем с добавяне на заявки в белия списък по-горе. След това ще активираме режима „ЗАЩИТА“:
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)
Това е. Сега можем да изпълняваме заявки от белия списък:
mysql> SELECT id, k FROM sbtest1 LIMIT 2;
+------+------+
| id | k |
+------+------+
| 7615 | 1942 |
| 3355 | 2310 |
+------+------+
2 rows in set (0.00 sec)
Но не можем да изпълним такива, които не са в белия списък:
mysql> SELECT id, k FROM sbtest3 LIMIT 2;
ERROR 1148 (42000): Firewall blocked this query
ProxySQL 2.0.9 идва с още една интересна функция за сигурност. Той има вградена libsqlinjection и можете да активирате откриването на възможни SQL инжекции. Откриването се основава на алгоритмите от libsqlinjection. Тази функция може да бъде активирана, като изпълните:
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)
Работи със защитната стена по следния начин:
- Ако защитната стена е активирана и потребителят е в режим ЗАЩИТА, откриването на SQL инжектиране не се използва, тъй като могат да преминават само заявки от изрично белия списък.
- Ако защитната стена е активирана и потребителят е в режим ОТКРИВАНЕ, заявките от белия списък не се тестват за SQL инжектиране, всички останали ще бъдат тествани.
- Ако защитната стена е активирана и потребителят е в режим „ИЗКЛЮЧЕНО“, се приема, че всички заявки са в белия списък и нито една няма да бъде тествана за SQL инжектиране.
- Ако защитната стена е деактивирана, всички заявки ще бъдат тествани за SQL взаимодействие.
По принцип се използва само ако защитната стена е деактивирана или за потребители в режим „ОТКРИВАНЕ“. Откриването на SQL инжектиране, за съжаление, идва с доста фалшиви положителни резултати. Можете да използвате таблицата mysql_firewall_whitelist_sqli_fingerprints за белия списък с пръстови отпечатъци за заявки, които са били открити неправилно. Да видим как работи. Първо, нека деактивираме защитната стена:
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)
След това нека изпълним някои заявки.
mysql> SELECT id, k FROM sbtest2 LIMIT 2;
ERROR 2013 (HY000): Lost connection to MySQL server during query
Наистина има фалшиви положителни резултати. В дневника можем да намерим:
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
Добре, нека добавим този пръстов отпечатък към таблицата с белия списък:
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)
Сега най-накрая можем да изпълним тази заявка:
mysql> SELECT id, k FROM sbtest2 LIMIT 2;
+------+------+
| id | k |
+------+------+
| 84 | 2456 |
| 6006 | 2588 |
+------+------+
2 rows in set (0.01 sec)
Опитахме се да изпълним натоварването на sysbench, това доведе до добавяне на още два пръстови отпечатъка към таблицата с белия списък:
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
Искахме да видим дали тази автоматизирана SQL инжекция може да ни защити срещу нашия добър приятел, 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)
За съжаление, не наистина. Моля, имайте предвид, че тази функция е базирана на автоматизирани криминалистични алгоритми, тя далеч не е перфектна. Може да дойде като допълнителен слой на защита, но никога няма да може да замени правилно поддържаната защитна стена, създадена от някой, който познава приложението и неговите заявки.
Надяваме се, че след като прочетете тази кратка серия от две части, имате по-добро разбиране за това как можете да защитите своята база данни срещу SQL инжектиране и злонамерени опити (или просто потребителски грешки) с помощта на ProxySQL. Ако имате още идеи, ще се радваме да чуем от вас в коментарите.