第14章 セキュリティ
アクセス権制御
MySQLはユーザーのアクセス権を制御します。ユーザーは特定の権限を持つことができ、これはユーザーがデータベース内で実行できる操作を制限します。次のSQLは、特定のユーザーに特定の権限を与える例です。
GRANT SELECT, INSERT, DELETE ON database_name.* TO 'user'@'localhost';
セキュリティを考慮する上で非常に重要な考え方であり、ユーザには適切に権限を与える必要があります。
ユーザーの作成
新しいユーザーを作成します。このユーザーはまだ何の権限も持っていません。
CREATE USER 'new_user'@'localhost' IDENTIFIED BY 'password';
データベースに対する全権限の付与
新しく作成したユーザーに、特定のデータベースに対する全権限を付与します。
GRANT ALL PRIVILEGES ON database_name.* TO 'new_user'@'localhost';
特定の権限の付与
新しく作成したユーザーに、特定のデータベースに対する特定の権限(この例ではSELECTとINSERT)を付与します。
GRANT SELECT, INSERT ON database_name.* TO 'new_user'@'localhost';
特定のテーブルに対する特定の権限の付与
新しく作成したユーザーに、特定のデータベースの特定のテーブルに対する特定の権限(この例ではUPDATE)を付与します。
GRANT UPDATE ON database_name.table_name TO 'new_user'@'localhost';
権限の確認
ユーザーに付与されている権限を確認します。
SHOW GRANTS FOR 'new_user'@'localhost';
権限の剥奪
ユーザーから特定の権限を剥奪します。
REVOKE SELECT ON database_name.* FROM 'new_user'@'localhost';
パスワードポリシー
MySQLはパスワードの強度をチェックする機能を提供します。これにより、弱いパスワードの使用を防ぐことができます。以下のコマンドは、パスワードポリシーを設定する例です。
SET GLOBAL validate_password.policy = LOW;
LOWはパスワードの長さだけをチェックします。MySQLのvalidate_password_length システム変数で設定した長さ以上である必要があります。
SET GLOBAL validate_password.policy = MEDIUM;
MEDIUMはパスワードに数字、大文字と小文字の英字、特殊文字が少なくとも1つずつ含まれていること、そして同じ文字が4文字以上続かないことを要求します。
SET GLOBAL validate_password.policy = STRONG;
STRONGはパスワード内にディクショナリ内の単語が含まれていないことをチェックします。ディクショナリの単語リストはvalidate_password_dictionary_file システム変数で指定します。
パスワードに関する変数
SET GLOBAL validate_password_length = 10;
validate_password_lengthはパスワードの最小長を設定します。上の例では、パスワードの最小長を10に設定します。
SET GLOBAL validate_password_policy = 'STRONG';
validate_password_policyはパスワードの複雑さを決定するポリシーを設定します。以下の例では、パスワードポリシーをSTRONGに設定します。
SET GLOBAL validate_password_mixed_case_count = 2;
validate_password_mixed_case_countはパスワードに必要な大文字と小文字の混在した文字の最小数を設定します。以下の例では、パスワードに必要な大文字と小文字の混在した文字の最小数を2に設定します。
SET GLOBAL validate_password_number_count = 2;
validate_password_number_countはパスワードに必要な数字の最小数を設定します。以下の例では、パスワードに必要な数字の最小数を2に設定します。
SET GLOBAL validate_password_special_char_count = 2;
validate_password_special_char_countはパスワードに必要な特殊文字の最小数を設定します。以下の例では、パスワードに必要な特殊文字の最小数を2に設定します。
SET GLOBAL validate_password_dictionary_file = '/usr/share/dict/words';
validate_password_dictionary_fileはSTRONGポリシーが設定されている場合に参照される辞書ファイルのパスを指定します。以下の例では、辞書ファイルのパスを'/usr/share/dict/words'に設定します。
各設定はSET GLOBALステートメントを使用して行いますが、これらの設定はMySQLサーバーの設定ファイル(通常はmy.cnfまたはmy.ini)にも記述することができます。その場合は、サーバーが起動するときに設定が読み込まれます。ただし、これらの設定はパスワードの安全性を強化するためのものであり、設定を強すぎるとユーザーがパスワードを忘れてしまうリスクがあるため、適度な設定にすることが重要です。
セキュア接続
MySQLでは、セキュアな接続を確立するために、SSL/TLS(Secure Sockets Layer / Transport Layer Security)というプロトコルを利用できます。これにより、クライアントとサーバ間での通信が暗号化され、盗聴やデータの改ざんを防ぐことができます。
以下に具体的な手順を説明します。
- SSL証明書の作成と設定:
まず、SSL通信を行うためにはSSL証明書が必要です。これは、OpenSSLなどのツールを使用して自己署名証明書を生成することができます。生成した証明書はMySQLサーバーの設定ファイル(my.cnfやmy.iniなど)に設定する必要があります。
[mysqld]
ssl-ca=/etc/mysql/certs/ca.pem
ssl-cert=/etc/mysql/certs/server-cert.pem
ssl-key=/etc/mysql/certs/server-key.pem
これにより、MySQLサーバーは起動時にこれらの証明書を読み込み、SSL通信を可能にします。
クライアント側では、MySQLサーバーへ接続する際にSSLを使用することを指定する必要があります。これは通常、接続文字列やコマンドライン引数で指定します。以下に、コマンドラインからMySQLサーバーにSSLで接続する例を示します。
mysql --ssl-ca=/path/to/ca.pem --ssl-cert=/path/to/client-cert.pem --ssl-key=/path/to/client-key.pem -u username -p -h hostname
このコマンドは、指定されたSSL証明書を使用してMySQLサーバーに接続します。
必要に応じて、MySQLサーバーは特定のユーザーに対してSSL接続の使用を強制することができます。これは、ユーザーアカウントを作成または変更する際にREQUIRE SSLオプションを使用して行います。例えば、以下のコマンドは、ユーザー'bob'がSSL接続を使用してMySQLサーバーに接続することを強制します。
GRANT ALL PRIVILEGES ON *.* TO 'bob'@'%' IDENTIFIED BY 'password' REQUIRE SSL;
これらの手順により、MySQLサーバーとクライアント間の通信は暗号化され、セキュリティが強化されます。ただし、SSL証明書の管理や、SSL接続のパフォーマンスオーバーヘッドなど、考慮すべき要素もあります。
SQLインジェクション対策
SQLインジェクションとは、攻撃者が悪意のあるSQL文をWebアプリケーションの入力フィールドに挿入することで、データベースに直接クエリを実行する脆弱性の一種です。これにより、不正なデータの抽出、変更、削除などが可能になり、重大なセキュリティリスクとなります。
SQLインジェクション攻撃を防ぐための主な手段は次のとおりです。
プレースホルダとバインド変数の使用
SQLクエリに変数を組み込む際には、プレースホルダと呼ばれる特殊な記号を使用します。その後、プレースホルダに対応する値をバインド(関連付け)します。この手法では、プレースホルダにバインドされる値は常にリテラル(文字列、数値など)として扱われるため、SQL文の一部として解釈されません。
以下にPHPとMySQLiを使用した例を示します
$stmt = $mysqli->prepare("INSERT INTO Employees (first_name, last_name) VALUES (?, ?)");
$stmt->bind_param("ss", $first_name, $last_name);
$first_name = "John";
$last_name = "Doe";
$stmt->execute();
$first_name = "Jane";
$last_name = "Doe";
$stmt->execute();
入力検証とサニタイズ
ユーザーからの入力を直接SQLクエリに使用する前に、入力を厳格に検証し、必要に応じてサニタイズ(クリーニング)することも重要です。例えば、数字のみを期待しているフィールドに対しては、入力が本当に数字であるかを確認し、そうでなければ拒否またはエラーメッセージを出力します。
ユーザーが入力した電子メールアドレスをデータベースに保存するときに、まずそのアドレスが有効な形式であるか確認します。その後、mysqliのreal_escape_string関数を使用して、SQLインジェクション攻撃を防ぐために特殊文字をエスケープします。
$email = $_POST['email'];
// 入力検証:有効な電子メールアドレスであるかを確認
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die("Invalid email format");
}
// サニタイズ:特殊文字をエスケープ
$email = $mysqli->real_escape_string($email);
// エスケープ後のメールアドレスをデータベースに保存
$query = "INSERT INTO users (email) VALUES ('$email')";
$mysqli->query($query);
最小特権の原則
アプリケーションがデータベースにアクセスする際には、そのアプリケーションが必要とする最小限の権限だけを付与するようにしましょう。例えば、特定のテーブルからデータを読み取るだけが必要な場合、そのアプリケーションに対しては、そのテーブルに対する読み取り専用の権限を付与します。
これらの手法を組み合わせることで、SQLインジェクション攻撃のリスクを大幅に軽減することができます。ただし、これらがすべての可能な攻撃を防ぐわけではないため、セキュリティは常に多層的に考えることが重要です。
データの暗号化
MySQLでは、データの暗号化について考慮することが重要です。データを暗号化することで、機密情報が不正に取得された場合でもその内容を保護することができます。MySQLでは、AES (Advanced Encryption Standard) という一般的な暗号化アルゴリズムをサポートしています。
以下は、AES_ENCRYPT と AES_DECRYPT 関数を使用してデータを暗号化および複合化する例です。
-- データベースに暗号化されたデータを挿入
INSERT INTO employees (first_name, encrypted_data)
VALUES ('John', AES_ENCRYPT('secret data', 'encryption_key'));
-- 暗号化されたデータを複合化して取得
SELECT first_name, AES_DECRYPT(encrypted_data, 'encryption_key') AS decrypted_data
FROM employees WHERE first_name = 'John';
ここでは、secret dataという文字列をencryption_keyというキーで暗号化しています。その後、AES_DECRYPT関数を使用して、同じキーで暗号化されたデータを複合化しています。
ただし、重要なこととして、暗号化キーは安全な場所に保存する必要があります。もしキーが第三者によって取得された場合、その人は暗号化されたデータを複合化することができます。
また、データを暗号化することにはパフォーマンス上のコストが伴いますので、これを考慮に入れて設計することも重要です。
さらに高度なセキュリティを求める場合、データベース全体を暗号化する Transparent Data Encryption (TDE) などの機能を利用することも可能です。TDEはデータベースエンジンのレベルでデータを暗号化・複合化する機能で、データはディスクに保存されるときに自動的に暗号化され、データベースから読み取られるときに複合化されます。ただし、TDEの利用はストレージやCPUに追加の負荷をかけますので、パフォーマンスに影響を与える可能性があります。