はじめに
本技術ブログでも過去何度か取り上げているデータベース専用プロキシソフトウェア ProxySQL
ですが、バージョン 2.0.9 にて、2つのセキュリティ新機能「ファイアーウォールホワイトリスト」と「SQLインジェクション解析エンジン」が実装されました。
- Implemented firewall whitelist
- Implemented SQL injection engine
引用:Releasing ProxySQL 2.0.9 – ProxySQL
※本稿執筆時点(2020年4月)での最新バージョンは 2.0.10 になります。
今回は、本家ProxySQLのマニュアルをベースに、このセキュリティ新機能の概要についてご紹介いたします。
ファイアーウォールホワイトリスト(Firewall whitelist)
DB観点でのファイアウォールは、クエリがDBMSで実行される前段で、許可された接続元やクエリかの判定を行ったり、悪意のあるアクセスや特定の(攻撃的な)クエリを弾く、というセキュリティ対策のための機能です。
主にWeb Application Firewall(WAF)が役割を担います。
MySQL Enterprise Edition では MySQL Enterprise Firewallが相当する機能となります。
ProxySQL 2.0.9 ではホワイトリスト形式のファイアーウォール(フィルタリング)が実装されました。
機能を利用するための2つのグローバル変数と3つの管理テーブルが追加されています。
グローバル変数
- mysql-firewall_whitelist_enabled
Firewall whitelist 機能の有効/無効を切り替えます。デフォルト値は”無効(0)”です。 - mysql-firewall_whitelist_errormsg
Firewall whitelist 機能によってブロックされた場合にクライアントに返すエラーメッセージを設定します。(既にmysql_query_rules.error_msg
が設定されていない場合に適用されるエラーメッセージになります)
管理テーブル
- mysql_firewall_whitelist_users
ホワイトリストとして登録するユーザー名、クライアントIPアドレス、ファイアーウォールモード(後述)を設定します。
設定自体は、active フラグで有効(1)/無効(0)を切り替えることが可能です。
クライアントIPアドレスは、ブランクを設定すると全IPアドレスを対象にすることができます。(残念ながら、サブネット単位での指定はまだサポートされていないとのことです) - mysql_firewall_whitelist_rules
mysql_firewall_whitelist_users テーブルの設定をスキーマレベル、クエリレベルで補完させるためのテーブルです。
mysql_firewall_whitelist_users の接続元 + 特定スキーマや特定クエリ(digestを指定)することでより厳密な許可設定を行うことができます。 - mysql_firewall_whitelist_sqli_fingerprints
このテーブルは後述するSQLインジェクション解析エンジンと関係するテーブルとなるので、後ほど説明します。
ファイアーウォールモード(mysql_firewall_whitelist_users.mode)
ファイアーウォールモードは、ホワイトリストの接続元1定義ごとに設定することができ、本機能のデフォルトアクションを決定する重要な設定となります。
モード | 説明 |
---|---|
OFF | 全てのクエリ実行を許可します。 |
DETECTING | 全てのクエリ実行を許可します。ただし、mysql_firewall_whitelist_rules に未登録のクエリの場合はProxySQLログファイル(prosyxql.log)にエントリーが出力されます。 |
PROTECTING | mysql_firewall_whitelist_rules に登録されたクエリのみ実行を許可し、それ以外のクエリはブロックされ、クライアントにはエラーが返されます。 |
DETECTING
モードにして、未知のクエリを検出したり、
PROTECTING
モード+ mysql_firewall_whitelist_rules
と設定することで完全なホワイトリスト形式の制御を実現することができます。
これまでのバージョンのファイアーウォールとしての使い方は…
クエリルーティングを定義する mysql_query_rules
テーブルを駆使して、ブラックリスト形式で明示的にブロッキングさせたり、一度全てのクエリをブロッキングルールとしてキャッチした後、実行許可してもよいクエリは例外としてバイパスさせる(=ホワイトリスト形式)ような定義ルールを作成することで実現は可能でした。
※Percona社の過去のブログでも紹介されていました。
ProxySQL Firewalling – Percona Database Performance Blog
ただ、この方式は問題を抱えており、フィルタしたい管理対象(ユーザー、クライアント、スキーマ)の数に比例して、mysql_query_rules
テーブルに作成する定義ルールが増大し、管理が非常に大変になります。
また、例外処理(mysql_query_rules
テーブルの flagIN
,flagOUT
,apply
を組み合わせたルールのチェーン)は、その設定自体が複雑なものですので、ホワイトリストとして色々な条件を判定させて許可させたい場合は、設定内容がさらに複雑化していってしまいます。
今回の新機能としての意味合いは、ファイアーウォールとしての機能を別管理(別設定)に分離し、利便性を向上させていると言えます。
Firewall whitelist の全体概要
Firewall whitelist 機能を有効にした場合のアルゴリズムを概略図でまとめてみますと、このような流れでフィルタリングが実行されることになります。
なお、既に mysql_query_rules
テーブルで特定のクエリのブロッキングルールを設定している場合、mysql_firewall_whitelist_rules
の設定よりも先にブロックが効きます。
混在させると複雑化してしまいますので、極力ファイアーウォール目的の設定はmysql_firewall_whitelist_rules
に移行したほうがベターと考えます。
SQLインジェクション解析エンジン(SQL injection engine)
libinjection
というSQL/SQLiトークン解析のCライブラリーが組み込まれ、SQLインジェクション攻撃を識別するメカニズムが備わりました。
libinjection
はオープンソースWAFとして有名なModSecurityなどで利用されています。
仕組み
機能を有効にするには、mysql-automatic_detect_sqli
パラメータを1
にセットします。
ただし、有効化したとしても、すべてのクエリに対してSQLインジェクション検出のための解析対象となるわけではなく、前述の Firewall whiltelist 機能が有効か(ホワイトリストに登録されているか否か)によって変わってきます。
解析対象となるケース
- Firewall whitelist 機能が無効になっている場合、全てのクエリは対象となります。
- Firewall whitelist 機能が有効になっている場合
DETECTION
モードで、ホワイトリストに登録されていないクエリは対象となります。
解析対象外となるケース
DETECTION
モード、またはPROTECTING
モードで、ホワイトリストに登録され明示的に許可されているクエリは安全と見做され、SQLインジェクション解析の対象外となります。PROTECTING
モードで、ホワイトリストに未登録のクエリの場合は、(SQLインジェクションか否か以前に)ブロックされるので対象外となります。- 端的に言えば、
PROTECTING
モードであれば対象外、ということになります。
- 端的に言えば、
- モード
OFF
の場合、全てのクエリがホワイトリストに登録されていると見做され、対象外となります。
文字だけでは分かりづらいので、ここまでの内容をまとめて、こちらも概略図にしてみます。
libinjection
は、クエリを解析すると、クエリーのフィンガープリントを生成し、フィンガープリントが既知のSQLインジェクション攻撃なのか、またはその可能性のある攻撃なのか、という判断を行います。
その結果、危険性のあるクエリと判断された場合は、実行が拒否され失敗となります。具体的には以下のようなエラーがクライアントに返ります。
1 2 |
mysql> select * from users where name = 'Mike" )) AND 1 = 1 AND (( "SnEc" = "SnEc' AND password = 'adlajgaeurpajsodaf'; ERROR 2013 (HY000): Lost connection to MySQL server during query |
proxysql.logにその結果が出力されます。この時のフィンガープリントが下記の例では Eoknk
になります。
2020-04-16 13:15:33 MySQL_Session.cpp:3402:handler(): [ERROR] SQLinjection detected with fingerprint of ‘Eoknk’ from client app_user@127.0.0.1 . Query listed below:
select * from users where name = ‘Mike” )) AND 1 = 1 AND (( “SnEc” = “SnEc’ AND password = ‘adlajgaeurpajsodaf’
ProxySQLのマニュアル記載でも言及されており、お気づきになったかもしれませんが、このSQLインジェクション解析エンジンは、攻撃の可能性
も含めて検出を行います。その結果、この機能は誤検知を多く発生させてしまう懸念をはらんでいます。
この誤検知を減らしていくための取り組みとしては、以下のいずれかの対策が推奨されています。
- proxysql.log で確認したクエリを
mysql_firewall_whitelist_rules
テーブルに登録し、ホワイトリストとして許可する。(恒久措置)
ホワイトリストに登録されていればそもそもSQLインジェクション解析エンジンによって処理されないためです。 - proxysql.log で確認したフィンガープリントの値を
mysql_firewall_whitelist_sqli_fingerprints
テーブルに登録し、active=1
とすることでSQLインジェクション解析エンジンがブロックするのを一時的に防ぐことができます。(暫定措置)
運用フロー(サイクル)を考えてみる
セキュリティ対策は継続的な取り組みが不可欠となります。前述したとおり、これまでのバージョンよりもファイアーウォール機能という観点で「利便性が向上している」理由として、幾つかの痒いところに手が届く機能を活用することができるところにもあります。
そのあたりも触れながら、全体的な運用フローがどのような流れになるか眺めてみます。
導入前の準備
- DBのトラフィックを可能な限り収集しておきます。
なにはなくとも、ホワイトリストの定義のために、まずは対象DBでどのようなクエリが流れるのかを把握する必要があります。
サービスイン前のシステムであれば、テストフェーズでクエリトラフィックを溜め込んでおくことが重要になります。ProxySQLはデフォルトでは、実行クエリの履歴やダイジェストをメモリー領域の
stats.stats_mysql_query_digest
テーブルに格納します。
※下の例では、Sysbench の実行クエリが記録されているレコードになります。条件の動的に変化するパラメータはプレースホルダ化されて記録されていることが確認できます。1234567891011Admin >SELECT hostgroup,username,digest,SUBSTR(digest_text,0,35),count_star,sum_time FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' ORDER BY sum_time DESC LIMIT 5;+-----------+----------+--------------------+------------------------------------+------------+----------+| hostgroup | username | digest | SUBSTR(digest_text,0,35) | count_star | sum_time |+-----------+----------+--------------------+------------------------------------+------------+----------+| 1 | sbtest | 0x9AF59B998A3688ED | SELECT c FROM sbtest2 WHERE id=? | 6198 | 3367288 || 1 | sbtest | 0xBF001A0C13781C1D | SELECT c FROM sbtest1 WHERE id=? | 5878 | 3272034 || 1 | sbtest | 0x4AC6CC3E8E66E2A5 | SELECT DISTINCT c FROM sbtest2 WHE | 629 | 995885 || 1 | sbtest | 0xC19480748AE79B4B | SELECT DISTINCT c FROM sbtest1 WHE | 593 | 974625 || 1 | sbtest | 0x2BD5CA9A9C3B517D | SELECT c FROM sbtest2 WHERE id BET | 602 | 772791 |+-----------+----------+--------------------+------------------------------------+------------+----------+5 rows in set (0.01 sec)このデータを継続的かつ永続的に保管しておく必要があり、
DISK領域として対応するテーブルstats_history.history_mysql_query_digest
へ INSERT … SELECT … するか、
または、SAVE MYSQL DIGEST TO DISK;
コマンドを実行するか、といった方法が従来までのバージョンでは想定されますが、
いずれにしても、定期的に実行する仕組みが必要でした。
ProxySQLにはスケジューラー機能が備わっていますのでジョブとして組み込むことは可能ですが、一手間掛かってしまいます。そこで、2.0.9 からは
admin-stats_mysql_query_digest_to_disk
(単位:秒) という変数が追加されました。admin-stats_mysql_query_digest_to_disk
を1以上に設定すると、設定秒数間隔でstats.stats_mysql_query_digest
テーブルからstats_history.history_mysql_query_digest
へ自動的にデータがダンプされる、という機能が有効になります。stats_history.history_mysql_query_digest
テーブルのdump_time
というINT型のカラムを使うことでダンプされた最新のダイジェストを確認する、といった使い方もできます。
※ダンプ後、stats.stats_mysql_query_digest
テーブルのほうはリセットされる動きになります。設定方法の例(1分間隔とする場合)は以下の通りです。
123Admin> set admin-stats_mysql_query_digest_to_disk = 60;Admin> LOAD ADMIN VARIABLES TO RUNTIME;Admin> SAVE ADMIN VARIABLES TO DISK;ダンプ実行のたびにメッセージがproxysql.logに出力されますのでご留意ください。
-
ホワイトリストの定義を確定する。
サービスのセキュリティ要件、アクセス許可リストなどを元に、実行許可するホワイトリストの定義内容を固めます。
運用開始時
- ホワイトリストユーザー定義を構成します。
mysql_firewall_whitelist_users
テーブルへ定義を登録します。
mysql_users
テーブルに登録済みの情報から INSERT … SELECT … で登録することも可能です。 -
ホワイトリストに登録する全てのユーザーとダイジェストを含むルール定義を構成します。
mysql_firewall_whitelist_users
テーブルへ定義を登録します。
stats_history.history_mysql_query_digest
テーブルに登録済みの情報から INSERT … SELECT … で登録することも可能です。 -
ホワイトリスト定義をランタイム適用(と設定永続化)
新たに追加されれた以下のコマンドを実行して、ProxySQLへ設定を適用(と永続化)を行います。
12Admin> LOAD MYSQL FIREWALL TO RUNTIME;Admin> SAVE MYSQL FIREWALL TO DISK; - Firewall whitelistを有効化します。
1Admin> set mysql-firewall_whitelist_enabled = 1;
- SQLインジェクション解析エンジンを有効化します。(必要に応じて)
1Admin> set mysql-automatic_detect_sqli = 1;
- 4,5 のグローバル変数の変更値を確定させます。
12Admin> LOAD MYSQL VARIABLES TO RUNTIME;Admin> SAVE MYSQL VARIABLES TO DISK;
導入後(実運用)の取り組み
継続的なアクションとして想定される取り組みをピックアップしてみました。
- DETECTING モードで検知したり、PROTECTING モードでブロックされた際の ProxySQL ログのエントリを監視し、ブロッキングが適切かどうかを判断します。
- 許可してもよいクエリはホワイトリストに追加登録したり、定義内容が陳腐化していないかといった見直しを定期的に行っていきます。
- SQLインジェクション解析エンジンによって摘発されたクエリについても同様にログエントリの監視を行い、誤検知の場合は定義内容の改善を行っていきます。
- アプリケーションの新規リリースや改修に伴ってクエリの追加や変更が発生する場合は、ホワイトリストへの登録や定義変更を開発やリリースのフローに含めます。
まとめ
ファイアウォール機能として、未知のパターンを持つ不正クエリを検知するためにはホワイトリスト形式の仕組みが非常に有効な手段となります。
(更にブラックリスト形式と組み合わせることでより柔軟にフィルタ出来るようになります)
MySQLの前段でセキュリティ対策を講じることが必要な場合は、ぜひ今回紹介させていただいたProxySQLのセキュリティ新機能の利用をご検討してみてはいかがでしょうか。