このブログの最初の部分では、ProxySQLを使用して、危険と見なされた着信クエリをブロックする方法について説明しました。そのブログで見たように、これを達成するのは非常に簡単です。ただし、これは完全な解決策ではありません。さらに厳重に保護されたセットアップを設計する必要がある場合があります。すべてのクエリをブロックしてから、一部のクエリのみを通過させることができます。 ProxySQLを使用してそれを実現することが可能です。それがどのように行われるかを見てみましょう。
ProxySQLでホワイトリストを実装する方法は2つあります。まず、歴史的なものは、すべてのクエリをブロックするキャッチオールルールを作成することです。これは、チェーンの最後のクエリルールである必要があります。以下の例:
すべての文字列を照合し、エラーメッセージを生成します。これは現時点で存在する唯一のルールであり、クエリが実行されないようにします。
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.
ご覧のとおり、クエリを実行することはできません。アプリケーションを機能させるには、実行を許可するすべてのクエリに対してクエリルールを作成する必要があります。ダイジェストまたはパターンに基づいて、クエリごとに実行できます。他の要因(ユーザー名、クライアントホスト、スキーマ)に基づいてトラフィックを許可することもできます。テーブルの1つへの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には、この特定のユースケースなどに焦点を当てた新しいアルゴリズムを含む新しいファイアウォールメカニズムが付属しています。効率的。使い方を見てみましょう。
まず、ProxySQL2.0.9をインストールする必要があります。パッケージはhttps://github.com/sysown/proxysql/releases/tag/v2.0.9から手動でダウンロードするか、ProxySQLリポジトリを設定できます。
これが完了したら、調査を開始して、SQLファイアウォールを使用するように構成してみることができます。
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」ユーザーを追加しました。特定のホストからの接続のみがファイアウォールルールに対してテストされていることを確認できます。また、ファイアウォールが使用されていない場合の「OFF」、不正なクエリがログに記録されるがブロックされない「DETECTING」、許可されていないクエリが実行されない「PROTECTING」の3つのモードを使用できます。
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には、さらにもう1つの興味深いセキュリティ機能があります。 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インジェクション検出は使用されません。
- ファイアウォールが有効で、ユーザーがDETECTINGモードの場合、ホワイトリストに登録されたクエリは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ワークロードを実行しようとしましたが、その結果、ホワイトリストテーブルにさらに2つのフィンガープリントが追加されました:
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インジェクションが、私たちの親友であるBoobyTablesから私たちを保護できるかどうかを確認したかったのです。
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)
残念ながら、そうではありません。この機能は自動化されたフォレンジックアルゴリズムに基づいているため、完璧にはほど遠いことを覚えておいてください。これは追加の防御層として提供される可能性がありますが、アプリケーションとそのクエリを知っている誰かによって作成された、適切に保守されたファイアウォールを置き換えることはできません。
この短い2部構成のシリーズを読んだ後、ProxySQLを使用してSQLインジェクションや悪意のある試み(または単にユーザーエラー)からデータベースを保護する方法をよりよく理解できることを願っています。他にもアイデアがありましたら、コメントでお聞かせください。