ProxySQL FirewallでMySQL本番環境のSQLインジェクションを阻止する

Database tutorial - IT technology blog
Database tutorial - IT technology blog

なぜアプリケーション層のセキュリティだけでは不十分なのか?

開発チームがいかに注意を払っていても、SQLインジェクション(SQLi)の脆弱性は、大規模システムにおいて常に付きまとう脅威です。この脆弱性は、リファクタリングが間に合っていないレガシーコードや、不用意に信頼してしまったサードパーティ製ライブラリを通じて入り込むことがよくあります。

アプリケーション層でのデータバリデーションだけに頼ることは、データベース全体の運命を開発者の注意力に委ねるようなものです。たった一度、Prepared Statementsの使用を失念しただけで、攻撃者は単純な検索クエリを使って全データをダンプできてしまいます。私は以前、フィルタリングパラメータが1つ漏れていただけで、5万件の顧客レコードが流出した事故を処理したことがあります。だからこそ、データベースの直前に第2の防波堤としてProxySQL Firewallが必要なのです。

データベース保護手法の比較

ProxySQLを設定する前に、現在の主要なソリューションを振り返り、なぜProxySQLが特別なのかを確認しましょう。

  • WAF (Web Application Firewall): CloudflareやModSecurityはHTTPリクエストのブロックには優れています。しかし、MySQLプロトコルを深く理解しているわけではなく、複雑なSQL難読化(obfuscation)技術によって回避される可能性があります。
  • データベースでのハードニング: ユーザー権限の制限(GRANT/REVOKE)は必須です。しかし、正当な権限を持つユーザーが乗っ取られ、大量のデータ削除コマンドを実行されることは防げません。
  • ProxySQL Firewall: Layer 7プロキシとして動作します。すべてのSQLステートメントを精査し、パターンと照合して、即座に「許可」「ブロック」または「書き換え(rewrite)」を判断します。

これら3つの層を組み合わせるのが最適ですが、ProxySQLこそが最も信頼できる最後の砦となります。

ProxySQL Firewall의仕組み

ProxySQLは、従来のファイアウォールのようにIPアドレスでブロックするのではなく、クエリの内容に基づいてブロックします。制御ルールを定義するために mysql_query_rules テーブルを使用します。

クエリが通過する際、ProxySQLはそれを match_pattern(通常は正規表現)と照合します。一致した場合、システムは以下のいずれかのアクションを実行します。

  • OK: 実行を許可する。
  • BLOCK: アプリケーションに即座にエラーを返し、リソース保護のためにデータベースへはクエリを送信しない。
  • REWRITE: クエリを送信する前に、安全な形に自動的に書き換える。

実践的な導入:危険なクエリの阻止

すでにロードバランサーとしてProxySQLクラスターを構築していると仮定します。以下の2つのステップで、それを本格的なファイアウォールへと変貌させましょう。

ステップ1:ブラックリスト(Blacklisting)の設定

これは、OR 1=1 のような典型的なパターンや、Web側からの DROP TABLE のような破壊的なコマンドをブロックする最も早い方法です。

-- 管理インターフェースにアクセス
mysql -u admin -padmin -h 127.0.0.1 -P6032

-- 'OR 1=1' を含むクエリ(ログイン回避によく使われる)をブロック
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, error_msg, apply) 
VALUES (100, 1, '(?i)OR\s+\d+=\d+', 'Access Denied: Potential SQL Injection Detected', 1);

-- アプリケーションユーザーからの DROP TABLE コマンドをブロック
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, error_msg, apply) 
VALUES (101, 1, '(?i)^DROP\s+TABLE', 'Access Denied: Dangerous command not allowed', 1);

-- 変更を即座に適用
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;

フラグ (?i) を使うことで、正規表現の大文字小文字を区別しないようにします。攻撃者が sqlmap などのツールを使用して調査を行っても、ProxySQLが即座にブロックし、カスタマイズされたエラーメッセージを返します。

ステップ2:ホワイトリスト(絶対的な安全戦略)

ハッカーは常にルールの裏をかく方法を見つけるため、ブラックリストだけでは不十分です。ホワイトリスト方式では、承認されたクエリのみを許可し、それ以外はすべて禁止します。

まず、ログモードを有効にして、現在実行されている「クリーンな」クエリを収集します。

-- 分析のためにクエリをログに記録
UPDATE mysql_query_rules SET log=1 WHERE rule_id=...; 

安全なクエリのリストを特定したら、それらを許可するルールを apply=1 で作成します。最後に、最大の rule_id を持つルールを追加して、残りのすべてをブロックします。

-- 最終ルール:未知のクエリをすべてブロック
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, error_msg, apply) 
VALUES (9999, 1, '.', 'Access Denied: Query not whitelisted', 1);

LOAD MYSQL QUERY RULES TO RUNTIME;

誤ブロックを防ぐために、アプリケーションが生成するクエリを完全に理解しておく必要があります。CSVからJSONにログを変換してホワイトリストの分析を高速化したい場合は、toolcraft.app/ja/tools/data/csv-to-json が便利です。このツールはクライアントサイドで動作するため、機密データがサーバーに送信されることはありません。

運用における実戦経験

この保護層を本番環境(Production)に導入する際は、サービスの中断を避けるために慎重に行う必要があります。

  1. 監視モード: 新しいルールを作成する際は、まず active=0 かつ log=1 に設定して様子を見ます。24〜48時間経過して問題がなければ active=1 に切り替えます。
  2. 優先順位: ProxySQLはIDの低い順にルールを適用します。具体的なルール(ホワイトリスト)を低いIDに、包括的なブロックルール(Catch-all)を最大のIDに配置してください。
  3. パフォーマンス測定: ProxySQLの正規表現処理は非常に高速で、通常はレイテンシへの影響は 0.5ms 未満です。ただし、数千もの複雑なルールがある場合は、システムを低速化させないために正規表現を最適化する必要があります。
  4. 継続的な監視: stats_mysql_query_rules テーブルを監視して、どのルールが異常にトリガーされているかを確認し、攻撃を早期に検知します。
-- どのルールが最も多くブロックしているかの統計を確認
SELECT rule_id, hits FROM stats.stats_mysql_query_rules;

Lời kết

ProxySQL Firewallの設定自体は難しくありませんが、それを維持するにはアプリケーションのソースコードを細かく追跡する丁寧さが必要です。これは、不注意なミスや意図的な攻撃からデータベースを守るための価値ある投資です。ログに DROP DATABASE コマンドが現れるのを待ってからセキュリティを心配し始める、ということがないようにしましょう。

Share: