免責事項
この記事はTodd Farmer氏によるMySQL Server Blogの記事「Protecting MySQL Passwords With the sha256_password Plugin」(2015/9/16)をユーザが翻訳したものであり、Oracle公式の文書ではありません。
長年にわたって、MySQLはパスワードの保存およびネットワーク通信の両者をセキュアにするのに3種類の方法を使ってきました。このブログ投稿では様々な方法の簡単な歴史をご紹介し、MySQLサーバー5.6で導入されたsha256_passwordを使うように移行すべき理由について焦点をあてます。
初期のパスワード(old password)
現在ではmysql_old_passwordとして知られている、初期のパスワードセキュリティーの仕組みは、様々な点でセキュアでないため、使うべきではありません(mysql_old_password認証プラグインは5.7.5でMySQLサーバーから削除されました)。強度の弱いハッシュアルゴリズムを利用しており、mysql.userテーブルに格納されているパスワードハッシュを取得した攻撃者はそのデータを使って認証することができます。要するに攻撃者がハッシュからプレインテキストのパスワードを見つけるのは比較的簡単であり、かつそのようなアカウントがあるMySQLサーバーにアクセスする為にはそうする必要すらありません。
この古いパスワードの仕組みを使っているアカウントは次のようにして見分けることができます。
- mysql.userテーブルのplugin列の値が「mysql_old_password」となっている。この列はMySQL 5.5のプラガブル認証で導入されましたので、MySQLサーバー5.5と5.6でのみ利用可能となります。
- mysql.userテーブルに格納されているパスワード列のハッシュが16文字となっている。これはパスワード列が削除されたMySQL 5.7までの全バージョンで成り立ちます。
後方互換の観点から、古い仕組みを過去の多くのリリースで利用可能としてきましたが、最終的には完全に廃止されます。この脆弱なパスワードの仕組みを依然として使っているアカウントがある場合は、今すぐに変更すべきです。
新しい(ネイティブの)パスワード
MySQL 4.1では新しいパスワードの仕組みを導入しました。MySQLサーバーの全てのバージョンでこれはデフォルトのパスワード方式であり、MySQLサーバー5.5でmysql_native_password認証プラグインで(デフォルトで有効化されている) 実装されています。この方式はSHA1ハッシュを利用しています。つまり、MySQL 4.1の初期の段階では安全と考えられていたアルゴリズムですが、現在では脆弱と認識されており いくつかのプロジェクトは数年以内に不正アクセスされるでしょう。また、その仕組みは保存されたパスワードハッシュが認証に使えないように改善されています。ネットワーク上のやりとりはランダムシードが含められ、攻撃者がシードとクライアントのパスワードハッシュの両者を知りえたとしても、ブルートフォースアタックに頼らずにパスワードを抽出することはできません。
これはすばらしいように聞こえ、初期のパスワード方式に比べて確実に改善されていますが、パスワードの保存に特筆すべき脆弱性があります。つまりハッシュは比較的計算が安価で、ある一貫した入力に対して一貫した出力を生成します。予測しやすさとスピードはセキュリティー上の敵です。
次の例は同じパスワードが同じハッシュを生成していることを実証しています。
mysql> CREATE USER u1@localhost
-> IDENTIFIED WITH mysql_native_password
-> BY 'pwd';
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> CREATE USER u2@localhost
-> IDENTIFIED WITH mysql_native_password
-> BY 'pwd';
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> -- Compare just the last 10 hex characters of
mysql> -- the hash for ease of reading:
mysql> SELECT
-> user,
-> plugin,
-> SUBSTR(authentication_string, -10) hash
-> FROM mysql.user
-> WHERE user IN ('u1', 'u2')\G
*************************** 1. row ***************************
user: u1
plugin: mysql_native_password
hash: 326D2021DD
*************************** 2. row ***************************
user: u2
plugin: mysql_native_password
hash: 326D2021DD
2 rows in set (0.00 sec)
特定のパスワード文字に対するハッシュは全く同一ですので、常に事前に計算されたレインボーテーブルを利用できます。これによって、攻撃者は盗んだハッシュ化されたパスワードを調べ、ハッシュ値がどのプレインテキストに対応するか確認するだけで良いのです。
レインボーテーブルを作成するためのコストはパスワード長および関数の複雑さに関連します。パスワードが長く、各文字列に多くの文字を使っていれば(小文字だけなのか、数字と記号を加えた大文字小文字なのか)、それだけ結果として作成されるレインボーテーブルが大きくなり、生成に時間がかかるようになります。
しかしこれはキーファクターを変えません。レインボーテーブルは1度計算しさえすればよく、どのMySQLサーバーに対しても利用可能です。仮にプレインテキストパスワードがレインボーテーブルを作成するときのデータセットに含まれていれば、ハッシュ値が分かればパスワードはほぼ瞬時に解読できるでしょう。
上記によりmysql_native_passwordを利用して保存されたパスワードはハッシュ値が知られていない場合に限り安全であると考えられます。mysql.userテーブルにアクセスできるか、CREATE USERかSET PASSWORD/ALTER USERコマンドのハッシュパスワードを含むログ(バイナリログ、監査ログ、一般ログ、スロークエリーログのいずれか)をみれるか、ハッシュ化されたパスワードにアクセスできるかすれば、プレインテキストのパスワードを取得しうるだろう、といえます。ユーザーは様々なシステムで同一、または似たパスワードを使うことはよくあることで、これはMySQLそのものの範囲を超えて影響を与えます。
なぜsha256_passwordの方がよいのか
sha256_passwordプラグインはMySQLサーバー5.6で導入され、パスワードの保存にフォーカスをあてて安全性を強化しています。どのように安全性を高めたかに関しては mysql_native_passwordを脆弱にさせている2つのキーとなる要素によって説明がつきます。1つはハッシュの計算がより高コスト/時間のかかるものとなっていること、そしてもう1つは出力がランダム化されていること、です。その上、より強度のあるSHA-256アルゴリズムを使い、脆弱なSHA1アルゴリズムへ依存しないようにしています。
sha256_passwordはあえてコストが高く時間がかかるように実装されています。すなわち結果としてえられるハッシュの生成を何千回か繰り返しています。ハッシュを作るのに時間がかかるようになっていますので、ブルートフォースアタックがしにくくなっています。
出力をランダム化するために、sha256_passwordはハッシュを生成する時にあるランダムソルトを利用しています。結果として、全く同じプレインテキストのパスワードを2回生成しても(実質的に常に)全く異なるハッシュ値がえられます。これは次の例で確認できます。
mysql> ALTER USER u@localhost
-> IDENTIFIED BY 'pwd';
Query OK, 0 rows affected (0.02 sec)
mysql>
mysql> -- Compare just the last 10 hex characters of
mysql> -- the hash for ease of reading:
mysql> SELECT
-> plugin,
-> SUBSTR(HEX(authentication_string), -10) hash
-> FROM mysql.user
-> WHERE user = 'u'\G
*************************** 1. row ***************************
plugin: sha256_password
hash: 48474F6534
1 row in set (0.00 sec)
mysql>
mysql> ALTER USER u@localhost
-> IDENTIFIED BY 'pwd';
Query OK, 0 rows affected (0.00 sec)
mysql>
mysql> SELECT
-> plugin,
-> SUBSTR(HEX(authentication_string), -10) hash
-> FROM mysql.user
-> WHERE user = 'u'\G
*************************** 1. row ***************************
plugin: sha256_password
hash: 5056353132
1 row in set (0.00 sec)
生成されたハッシュは、生成される際に利用されたソルトに対してユニークとなりますので、レインボーテーブルの作成が非効率になります。レインボーテーブルの各エントリは、ターゲットとするハッシュで使われたものと全く同じソルトで生成されたものでなければいけません。これにより事前に計算されたレインボーテーブルが使える可能性を排除します。すなわち利用されているソルトを知りえない限り、ハッシュ値の等しい候補となるパスワードを計算し始めることが出来ないのです。
ソルト自体は保存されたハッシュに埋込まれています。これは簡単に抽出され、同一のシードとユーザーの指定したプレインテキストパスワードからハッシュを再計算して、保存されたハッシュと一致するかどうか確認する目的で使われます。 一致するかどうか確認する為に攻撃者がパスワードを推定できるか、一握りの共通パスワードが一致するかどうか確認すれば良いだけであれば、このパスワードを保存する上での改善はほとんど影響がないでしょう。しかしユーザーがベストプラクティス(大文字小文字および記号から構成される辞書にある単語を使わない長いパスワード)をふまえてパスワードの設定しているなら、パスワードが一致するかどうかをテストするには非常に時間のかかるようになっています。適切なパスワード変更ポリシーを採用することで、アタックウィンドウを限定することができます。攻撃を開始しうるタイミング(攻撃者がソルトを入手したとき)および、いつ攻撃を完了する必要がある(ユーザーがパスワードを変更し、新しいハッシュおよびソルトが生成されたとき)についての制約です。
ネットワーク通信
いくつかのトリックを使うことで、mysql_native_password方式はトランスポートレベルセキュリティーを必要とすることなしに、ネットワーク越しにパスワードを安全に通信させることができます。MySQLサーバーに保存されているパスワードハッシュはプレインテキストパスワードをSHA1で2度ハッシュ化することで生成されます。クライアントがパスワードをサーバーに送信したときに、3つの情報を利用します。
- プレインテキストパスワードのSHA1ハッシュ
- プレインテキストパスワードのSHA1ハッシュのSHA1ハッシュ
- ハンドシェイクの過程でMySQLサーバーに送られたランダムシード
内部構造に関するマニュアルに記載されている通り、サーバーに実際送られているのは次の通りです。
#1 xor SHA1( #3 concat #2)
サーバーからのランダムシード(#3)は攻撃をリプレイできないようにしています。つまりクライアントの認証レスポンスは誰も記録できませんし、新しい接続を確立する為にリプレイすることも出来ません。認証プロセスのペイロードはランダムシードが異なるため認証プロセス毎に異なります。
サーバーはクライアントに送られたランダムシードを認知しており、SHA1(SHA1(プレインテキスト))の値を知っています。なぜならば、mysql.userテーブルに保存されているからです。XOR操作を逆方向に行いSHA1(プレインテキスト)を取得できます。それをSHA1でもう一度ハッシュ化するとSHA1(SHA1(プレインテキスト))になるはずで、mysql.userに保存されているものとマッチします。クライアントはmysql.userに保存されている2回ハッシュ化されたパスワード以上のことを知っていると分かります。
これはサーバーが保存されたハッシュ化パスワードを認知しており、クライアントはハッシュ化パスワードを生成できるということに基づいています。言い換えれば、この仕組みには一貫性が必要といえます。したがって、sha256_passwordはセキュアにパスワードを通信させるためにこれらの同じトリックを利用できません。すなわち、保存されたハッシュを生成するためにはアカウントのソルトが必要となります。シードをクライアントに送信することは技術的には可能ですが、想定したユーザー名が存在するかどうかを決めるためにその値を監視されてしまいます(もしそうであれば、パスワードが変更されるまで一貫して同じソルト値を送ることでしょう )。このような情報が漏れることを防ぐために、サーバーは全てのハッシュ化操作をサーバー自身で行っており、これはクライアントからのパスワードはプレインテキストで取得する必要があることを意味しています。
クライアントからサーバーに送信されるパスワードをプレインテキストで取得するのには問題があります。従ってトランスポート層のセキュリティーが必要となります。これはsha256_passwordに対して2つの異なる方法で実装されています。すなわち、SSL/TLSあるいはRSAキーペアを利用したものです。もしこれらのオプションがいずれも有効化されていない場合は、sha256_passwordプラグインはクライアントからセキュアにパスワードを受け取れないことを検知し、認証に失敗します。
RSA公開/秘密鍵暗号化は、パスワード交換が暗号化される場合に限って有用です。通常操作といった他の点では暗号化による性能オーバーヘッドの影響を受けません。しかしながら、この機能はCommunity Editionで利用されているyaSSLライブラリではなく、OpenSSLライブラリを使用します(ライセンス互換の問題によります)。これにより、RSAを使ってセキュアなパスワード交換をする場合はEnterprise Editionを使うか、もしくはyaSSLの代わりにOpenSSLを使ってパッケージを特別にビルドする必要があります。
RSAを利用する場合はユーザビリティーの問題も発生します。サーバーのRSA公開鍵を使ってパスワードを暗号化するために、クライアントはまずこのキーを取得する必要があります。キーをセキュアに配布することが運用上の悩みの種となるかもしれません。MySQLサーバーは、鍵を明示的に配布したり設定したりする必要が無いようにクライアントからのリクエストに対してRSA公開鍵を配ります。しかしこれは別のセキュリティーの問題を引き起こします。つまり中間に位置するプロキシがRSA公開鍵の代わりをつとめ、復号化しプレインテキストを取得し、そしてコネクションを継続するために実際のサーバーのRSA公開鍵で再度暗号化する可能性があります。この理由から、ハンドシェイクの時にサーバーのRSA鍵をリクエストするのではなく、クライアントはローカルRSA公開鍵を定義して使うことが強く推奨されます。
SSL/TLSオプションに関しては特に注意すべき点はありませんし、正しく設定されていればすべてのユーザーがご利用になれます。SSL/TLSを簡単に利用できるようにしたのはMySQLサーバー5.7のキーとなるセキュリティー上の重点項目で、それはsha256_passwordはパスワードの保存に優れていますが、SSL/TLSが設定されていない(そしてRSAが利用可能でない)為誰も接続できない場合は、その有用性が限定的になってしまうからです。
パスワードのセキュリティーに真剣に取り組むなら、セキュアなコネクションを利用する必要があります。上記のネットワーク上のパスワード交換のディスカッションは、認証の通信をセキュアにするという範囲に限ります。パスワードはその他の多くの操作で外部にさらされ、例えばユーザーアカウントのメンテナンス、レプリケーションスレーブまたはFEDERATEDテーブルの設定、などがあります。これはMySQLサーバーのアカウントパスワードに限った問題ですし、例えばMySQLサーバーがウェブアカウントの情報を保存するのに利用されていた場合、他の多くの操作でプレインテキストパスワードや、他の秘密情報が外部にさらされかねません。ネットワーク上の盗聴者はコネクションがセキュアな通信を利用していなければ、そのような情報を簡単に取得できます。
結論
sha256_password認証プラグインはmysql_native_password認証システムの根本的な脆弱性に対処するために設計されました。もしログ、ストレージ、SQLインジェクション攻撃など何らかの形でパスワードハッシュがもれる懸念がある場合は、これらのハッシュをよりセキュアにするsha256_passwordを利用することを検討する必要があります。mysql_native_passwordはトランスポート層のセキュリティーに頼ることなくセキュアにパスワード交換をする単一の仕組みを利用しており、これは通信の安全性を犠牲にして安全なパスワードの保存を効率的に行い、その結果レインボーテーブルを使ったクラッキングの影響を受けやすいでしょう。mysql_native_passwordの仕組みはSHA1アルゴリズムに依存しており、これは脆弱であることが知られており、近未来に突然不正にアクセスされるかもしれません。さらに、SSL/TLSを全コネクションで利用することを考慮すべきで、これはmysql_native_passwordのセキュアなパスワード交換の仕組みは認証プロセスのみを対象としており、その他の安全性が担保されるべきコマンド(例えば、アカウント、レプリケーションのメンテナンス操作)は保護されないためです。