はじめに
この連載では、オープンソースのリレーショナル・データベース(RDB)の「MySQL」と「PostgreSQL」を使いこなすヒントを、両製品の共通点と違いを確認しながら解説していきます。
現在、それぞれの製品をベースとしたクラウド・データベースは複数存在しています。この連載では製品の比較を多く行いますが、製品機能や仕様の優劣についてを論ずる目的のものではないことはあらかじめご了承ください。
第4回の今回は、MySQLとPostgreSQLの各種のセキュリティ要件への対応について解説します。第2回では通信経路の防御や認証認可、第3回ではデータの暗号化方式について解説しました。今回は以下の機能を解説します。
- データベースへの代表的な攻撃であるSQLインジェクション対策
- 不正アクセスの検出や操作の記録を行うデータベース監査
- 機密情報や個人情報を隠蔽するためのマスキング
SQLインジェクション対策機能
SQLインジェクションはデータベースに対する代表的な攻撃です。SQL文に不正な命令を混入させ、データの不正な入手や改変を行います。
特定のデータベース製品に限らず、SQLインジェクション対策の基本は「アプリ側で安全なSQL文を組み立てること」です。不正な命令がSQL文に混入しないよう、入力値のバリデーションを行います。文字列連結でSQL文を作らず、プリペアド・ステートメントやO/Rマッパーを利用することも考えられます。
また、SQLインジェクションの対策に限定されることではありませんが、ロールに最小限の権限だけを持たせることやスキーマの分離も検討事項に挙げられます。
アプリケーションとデータベースの間にデータベース・ファイアウォール製品を導入するケースも考えられます。MySQLとPostgreSQLに対応した製品の一例は下記です。
- Imperva Database Security
- IBM Security Guardium
- DataSunrise Security Suite
- Oracle Audit Vault and Database Firewall
いくつかの製品は、データベース・ファイアウォール機能だけではなく、監査や暗号化、統合監視など複数の機能を包含しているものもあります。
MySQLでは、商用版のEnterprise Edition限定にはなりますが、プロキシ・サーバー的に動作するデータベース・ファイアウォール製品とは異なり、データベース内で動作するファイアウォールである「MySQL Enterprise Firewall(MEF)」が用意されています。アプリケーションから直接MySQLサーバーに接続してSQLを実行できる点が大きなメリットです。
MEFには3つのモードが用意されています。
- 記録モード: 実行されたSQLを許可リストに追加
- 保護モード: 許可リストにないSQL文を拒否
- 検知モード: 許可リストにないSQL文をエラーログに記録し、実行は許可
許可リストはMySQLサーバー内のデータベースごとに作られるfirewall_group_allowlistテーブルに、SQL文を実行するユーザー・アカウントと「正規化された」SQL文の組み合わせで記録されています。この「正規化された」SQL文はデータベース設計における正規化とは関係がなく、パフォーマンス・スキーマでSQL文を管理するSQL Digestと同じく変数を「?」に変換し、大文字小文字の区別や空白などを無視する形で整形された形式です。
【例】 MySQLでの「正規化された」SQL文
-- 実行されたSQL文
SELECT Name, Population FROM city WHERE CountryCode = 'JPN' AND Population > 1000000;
SELECT Name,Population FROM City WHERE CountryCode = 'CAN' AND Population > 500000;
-- 「正規化された」SQL文
SELECT `Name` , `Population` FROM `city` WHERE `CountryCode` = ? AND `Population` > ?;PostgreSQLの拡張機能としてオープンソースで提供されているデータベース・ファイアウォールは確認できていませんが、EDB Postgres Advanced Serverには「SQL/Protect」がMEFと類似した機能を提供しています。
データベース監査
データベース監査とは「いつ」「誰が」「どのデータに」「どの操作を行ったか」を記録する仕組みです。これにより操作の追跡が可能となり、内部不正や外部侵害の検知や原因調査に役立つほか、セキュリティ関連の法令や規制、各企業の社内ポリシー遵守の証跡として活用されます。
MySQLもPostgreSQLも実行される全てのSQL文をログに出力できます。MySQLの場合は一般ログの利用、PostgreSQLの場合はサーバーのログ出力設定でのlog_statement=allの指定が該当します。ただし、どちらの場合も出力されるデータ量が膨大になり、ストレージを圧迫し性能のオーバーヘッドも無視できなくなります。
また、MySQLの一般ログではSQL文を実行するユーザー・アカウントの追跡が難しく、PostgreSQLもプリペアド・ステートメントを利用した場合などに実際に実行された処理が判別しにくくなることがあります。さらにそれぞれのログや設定はデバッグ用途などで利用されることが想定されており、監査が必要なSQL文や処理に絞り込むことや、ユーザーごとに異なるフィルタリングができる監査用のログが別途必要とされる場面が多くあります。
MySQLは、こちらも商用版のEnterprise Edition限定で「MySQL Enterprise Audit」が提供されています。監査ログとして記録する対象のユーザー・アカウントや命令の種類(参照、更新・権限変更など)、SQL文などでフィルタリングも可能です。リモートのサーバやOracle Audit Vaultなどの監査データを管理する外部ツールとも連携できます。
PostgreSQLには拡張機能として「pgaudit」が用意されています。pgauditではSQL文やPostgreSQLのコマンドを対象にログの記録を行う「セッション監査」と、テーブルやビュー単位で設定を行う「オブジェクト・レベル監査」の機能があります。
pgauditは設定ファイルpostgresql.conf内のパラメータpgaudit.logで監査ログに出力するSQL文のクラス(カテゴリ)を指定できます。複数のクラスをカンマで区切って並べる指定もでき、また-を先頭に付けて除外することも可能です。
表1:監査ログに出力するSQL文のクラス
| クラス | 対象 |
READ | データを参照するSQL文: SELECTおよびデータを抽出する側のCOPY |
WRITE | データを変更するSQL文: INSERT,UPDATE,DELETE,TRUNCATEおよびデータを登録する側のCOPY |
FUNCTION | 関数呼び出しおよびDOブロック |
ROLE | 権限関連のSQL文: GRANT,REVOKE,CREATE/ALTER/DROP ROLE |
DDL | ROLクラス以外の全DDL |
MISC | 各種コマンド: DISCARD,FETCH,CHECKPOINT,VACUUM,SETなど |
MISC_SET | 各種SETコマンド:【例】SET ROLE |
none | 監査ログを出力しない |
またALTER文でPostgreSQL全体、特定のデータベース、またはロール単位での出力の制御が可能です。
【例】ALTER文で監査ログに出力するSQL文のクラスを指定する例
-- ユーザー postgres による全SQL文を記録する
ALTER ROLE postgres SET pgaudit.log='ALL';
-- ユーザー scott によるデータの参照更新を記録する
ALTER ROLE scott SET pgaudit.log='READ,WRITE';pgauditではSELECT、INSERT、UPDATEおよびDELETE限定でテーブルおよびビューでのフィルタリングも可能な「オブジェクト・レベルの監査ログ」も利用できます。
このオブジェクト・レベルでのフィルタリングを利用するには、まず監査用ユーザーを用意し、その監査用ユーザーを設定ファイルのpgaudit.roleに指定するかSET文で指定します。その上でこのユーザーにテーブルや列に対する監査を行いたいSQL文の種類を指定します。
【例】監査用ユーザーをauditorAとし、cityテーブルに対する全SELECTをログに記録する例
-- set文でpgaudit.roleを指定
set pgaudit.role = 'auditorA';
-- 対象のテーブルに対する権限を指定
grant select on city to auditorA;注意すべき点は、ここでpgaudit.roleに指定するユーザーはアプリケーション側のユーザーではなく、あくまでも監査用に用意した専用のユーザーである点です。
なお、pgauditは開発の経緯により、pg_auditというプロジェクト名の時期や、同じpgauditという名称でも旧2ndQuadrant社のプロジェクトなどもあるため、現在最も開発が活発なpgauditのレポジトリのものを選択することをおすすめします。
データを隠蔽するためのマスキング
データのマスキングはGDPR(EU一般データ保護規則)やPCI DSS(Payment Card Industry Data Security Standard)など、多くのセキュリティ規約で求められる「データ保護」の項目の一つとなっています。マスキングと暗号化はどちらも「データ保護」ですが、対策したいリスクと利用する場面が異なります。
表2:データ・マスキングとデータ暗号化の比較
| マスキング | 暗号化 | |
| 概要 | 見せる値の置き換え | 値そのものを書き換え |
| 目的 | 閲覧による情報漏えい対策 | データの窃盗対策 |
| タイミング | データの表示時 | データの保存、転送、および利用時 |
| 見せ方 | ユーザーによって見る値を変えることも可能 | 基本的に復号すれば平文で見える |
マスキングを利用する最も基本的な場面は画面や帳票、ログに機密情報を表示したくない時です。またサポート業務などで機密情報を含む本番データを見る必要がない場合や、本番のコピーを開発や環境に移す場合、また機密情報以外のデータを使って分析を行う場合などもマスキングが求められます。
MySQLとPostgreSQLではマスキングの実装が大きく異なっています。
現時点でのMySQLでは、商用版のEnterprise Edition限定でマスキング用のSQL関数を提供しています。この「MySQL Enterprise Masking and De-identification」は、アプリケーションがSELECT文の中で明示的にマスキング用のSQL関数を実行するか、SQL関数を使用したビューを用意するという使い方になります。対象データの一部の指定した文字数を伏せ字にするmask_inner()やmask_outer()のほか、フォーマットが決まっているデータをフォーマット固有のルールに従ってマスクする関数群もあります。
- mask_ssn(): アメリカのソーシャル・セキュリティー・ナンバー
- mask_canada_sin(): カナダのSocial Insurance Number
- mask_uk_nin(): イギリスのNational Insurance Number
- mask_iban(): 銀行口座の所在国、銀行名、支店名および口座番号を特定するための国際規格コード
- mask_pan(): クレジットカード番号
- mask_uuid(): UUID (Universally Unique Identifier)
【例】MySQLのマスキング関数の利用例
-- 2文字目から2文字をデフォルトの伏せ字 "X" でマスキング
mysql> SELECT mask_inner("かすみがうら市", 1, 2);
+----------------------------------+
| mask_inner("かすみがうら市", 1, 2) |
+----------------------------------+
| かXXがうら市 |
+----------------------------------+
-- 3文字目から2文字を指定した伏せ字 "#" でマスキング
mysql> SELECT mask_inner("かすみがうら市", 2, 2, "#");
+---------------------------------------+
| mask_inner("かすみがうら市", 2, 2, "#") |
+---------------------------------------+
| かす###ら市 |
+---------------------------------------+この機能にはマスキング関数に加えて、架空のデータを生成する関数群も用意されています。また上記のフォーマットに加えて、アメリカの電話番号やeメールのフォーマットに合わせたランダムなデータ、および特定の範囲の数値データを生成できる関数があります。独自の辞書を作成し、その辞書内のデータをランダムなデータの元とすることもできます。
PostgreSQL向けには、フランスのDalibo社が開発する拡張機能「PostgreSQL Anonymizer」(拡張機能名:anon)があります。anonはPostgreSQLのセキュリティラベルを利用して、ユーザー(ロール)ごとにマスキングの有無などを設定できます。
anonには不要になった機密情報を元データの値そのものを伏せ字などに書き換えるスタティック・マスキングもありますが、一般的なマスキングの用途としては元データは書き換えないダイナミック・マスキングが利用されることになります。
【例】anonのダイナミック・マスキングの利用例
-- 拡張機能 anon のインストールとデータベースfooでの有効化
CREATE EXTENSION IF NOT EXISTS anon CASCADE;
ALTER DATABASE foo SET anon.transparent_dynamic_masking TO true;
-- peopleテーブルのname列を架空の名字を返す形でマスキング
SECURITY LABEL FOR anon ON COLUMN people.name
IS 'MASKED WITH FUNCTION anon.dummy_last_name()';
- ユーザーscottが上記の列を参照した際にはマスクされたデータを表示
SECURITY LABEL FOR anon ON ROLE scott IS 'MASKED';
GRANT pg_read_all_data to scott;この例では、scottがpeopleテーブルのname列の値を参照するとanon.dummy_last_name()が生成した架空の名字が表示されるようになります。この列にアクセスできる他のユーザーには元データのまま表示されます。
anonでは部分的に伏せ字にするanon.partial()やanon.partial_email()は2種類のみですが、ランダムなデータの生成や実在しそうな架空のデータでの値を置き換えるための関数が充実しています。特に7つのロケール向けには、オープンソース・プロジェクトの「fake-rs」のデータを利用しており、日本に関してはfake-rsにデータが用意されている氏名や電話番号がよりリアルな架空データが利用できます。
また、anonは単にテーブルの参照時のマスキングだけではなく、ロジカル・レプリケーションやダンプ時に自動的にマスキングすることも可能になっています。
おわりに
今回はMySQLとPostgreSQLのセキュリティに関するテーマのうち、SQLインジェクション対策、監査、マスキングについて解説しました。いずれも重要な機能ですが、比較してみると機能や仕様には両製品の間で違いが目立つ形となっています。
次回は両製品のセキュリティ関連の項目のうち、データベースを安全に利用する指針やバージョン管理についての解説を予定しています。
