目次
Webアプリケーションの認証方法について、ざっくり整理してみる
別記事で、Webアプロケーションの脆弱性をざっくりとまとめました。
ただ、認証ところが、ページ数の関係で不十分でしたので、続きを整理していきます。
認証/認可のいろいろなタイプ
認証とひとくちに言っても色々な種類があります。
主だったところでいうと。
- ベーシック認証(基本認証)
- ダイジェスト認証
- セッションベースの認証
- トークンベースの認証
- ワンタイムパスワード認証
- OAuth/OAuth2またはOpenID
です。
それぞれに長所・短所があり、Webアプリケーションのタイプ別に相性があります。
判断目安の一例は
- 一般的なWebアプリケーション:セッションベースの認証
- SNS等へのシングルサインオンが必要な場合:OAuthまたはOpenIDを追加
- 気密性の高いデータ処理が必要な場合:ワンタイムパスワードを追加
- RESTful APIの場合:トークンベースの認証
です。。
続けて、個別にざっくり整理していきます。
ベーシック認証とダイジェスト認証
どちらもHTTPで定義される認証(HTTP認証)です。
すべてのブラウザでサポートされていて、かつ、実装も簡単です。
しかし、ログイン後も、リクエストごとにAuthorizationヘッダがブラウザによって付与されて、IDとパスワードの情報が毎回送出される動きをするので、セキュリティを気にするWebアプリケーションでは使いづらい面があります。
特にベーシック認証は、IDとパスワードを含む情報をプレーンテキスト(Base64形式エンコード)で送るので、超不安です。
ダイジェスト認証は、情報をハッシュ化(MD5)して送るので、ベーシック認証より相当マシですが、毎回IDとパスワードを送る点は同じです。
あと、ログアウトの概念がありません。
とはいえ。
仲間うちで使うツール的なWebアプリやデモンストレーション用画面とかのように、それほどセキュリティに神経質になる必要がないものであれば、手軽に使える認証方法ではあります。
セッションベースの認証
アプリケーションには、入力以外の情報を内部保持して、入力+内部保持情報で処理して結果を返す「ステートフル」なものと、内部情報はもたずにその時の入力だけで処理して結果を返す「ステートレス」なものがあります。
Webアプリケーションは「ステートレス」が基本です。
なので、サーバー側でアプリケーションの状態を覚えておく(ステートフルにする)ための「セッション管理」がクッキー(cookie)の仕組みを使って導入されています。
セッションベースの認証は、その仕組みを使って、ログイン者の認証状態をサーバー側に保持しておくやり方です。
セッション管理で保持した状態にアクセスするキーとして、セッションIDを使います。
セッションベースの認証ではログインの成功したら新たにセッションIDを発行してブラウザ側に送り、あとは、そのセッションIDをやり取りすることで、認証状態を維持していくやり方をとります。
実装も比較的簡単で、ユーザにとっても使いやすく優れた方法です。
でも、当たり前ですが、セッションIDが漏洩すると終わりです。
それを防ぐための主なチェックポイントは。
- クッキー発行の際の属性に不備がないか(特にDomain、Source、HttpOnlyなど)
- セッションIDをURLに保持などしてRefererヘッダから漏洩するリスクがないか
- TLS等で暗号化されていないためネットワークの盗聴で漏洩するリスクがないか
- セッションIDが推測可能になっていないか(セッション管理機構の自作はNG)
とかになります。
もちろん、使用言語やブラウザ、アプリケーションの脆弱性への攻撃などなど、漏洩リスクは他にもあるので、上記ですべてというわけにはいきませんが。
あと。
セッションベースの認証は「CSRF(クロスサイト・リクエストフォージェリ)攻撃」に対して弱いというのが一般的見解で、かならず、CSRF対策とあわせて使う必要がある点も、重要なポイントです。
CSRFに関しては前回の記事に軽く書いてますので、そちらをどうぞ。
トークンベースの認証
セッションIDのやりとりではなく、トークンを用いる方法です。
トークンで一般的に使用されるのが、JWTです。
JWTは「JSON Web Token」の略です。
上記のような情報をJOSN形式で記述して、それをBase64でエンコードしてから、秘密鍵とハッシュアルゴリズムを用いて署名を作成して、署名なしTokenと結合する・・みたいな手順で作成した「トークン」を認証につかいます。
トークンベースの認証では、ログインに成功したらトークンを生成してクライアントに送ります。
あとは、クライアントからのリクエストに添えられてくるトークンを秘密鍵を使い署名が正しいことを確認することで認証します。
サーバー側に認証情報を置く必要はありません。
認証の仕組みを、基本「ステートレス」で実現できます。
こういう仕組みなので、トークンがリークしたら面倒なことになります。
なので。
みたいなことがチェックポイントになります。
そのなかでも、特に最後の「送る方法」が重要なので補足します。
JWTを送る方法には、 HTTP の Authorization ヘッダなどに含める方法とCookie に含める方法があります。
Authorization ヘッダに含める方法の場合、JavaScriptなどからアクセス可能な場所にJWTトークンがおかれて、その場合、XSS(クロスサイドスクリプティング)攻撃などで、トークンがリークされる可能性があります。
Cookieに含める方法は、CooieにHttpOnly属性をつけてJavaScriptからのアクセスを止めることができるので、比較的安全で、推奨方法になってます。
だけど、Cookieを使ったセッション管理自体がCSRF攻撃に弱いので、その対策もちゃんとやっておく必要があるのは言うまでもないです。
ちなみに。
認証状態を維持する「自動ログイン」の手法として、都度、もう少しシンプルなトークンを発行し、その情報をDBに保持しておいて、リクエスト時に送られてきたトークンをDBの情報と比較して認証する簡易的なトークンベースのやり方もあります。
このやり方は、DBの情報を消すことで「ログアウト」処理もできるし、やりとり都度新たなトークンを発行するようにしたらセキュリティ的にも比較的強く、普通のWebアプリケーションにも使えるので、個人的には気に入ってます。
ということで、補足程度に書いておきます。
ワンタイムパスワード認証
OTPともいいます。
認証が必要なときに、サーバー側でランダムなコードを生成して保存したのちに、ユーザの「信頼できる端末」にそれを送り、ユーザはそれを入力して認証をうけます。
その場限りのパスワードなので送る先が「信頼された端末」である限りは、鉄壁に近い強度でセキュリティを守ることができます。
ワンタイムパスワードを利用するシステムに何回かかかわったことがありますが、その倍はすべて小さな「ワンタイムパスワード専用端末」を貸与されて、そこに表示される文字列をうちこんでログインしてました。
最近は、送る相手が「スマホ」だったりする場合もあるみたいですが、その場合、どういう方法で「信頼できる端末」であることを保証するか?が最大の問題です。
OAuth/OAuth2またはOpenID
OAuth / OAuth2とOpenIDは、Facebook、Twitter、Googleなどのソーシャルネットワーキングサービスからの既存の情報を使用して、シングルサインオン(SSO)の形式であるソーシャルログインを実現する手段です。
多くの場合、セッションベースの認証との組み合わせで使います。
それぞれ違うのですが、ここからはOAuthをイメージして書きます。
この仕組みを、超ざっくりいえば、決められた所定の手続きで「登録証明書」や「利用許可証」にあたるトークンをえることで、それを使ってカギにあたる「アクセストークン」と利用者証明にあたる「トークンシークレット」を申請・取得できるようになるというものです。
実際のデータへのアクセスは「カギ」と「利用証明」を使って行います。
Twitterに書き込んだら、自動的に画像共有サービスにも登録する・・みたいなユーザに優しい便利機能を提供できる、とても便利な仕組みですけど、危険な面もあります。
たとえば、悪意をもった攻撃者が、ゲームや画像共有サービスなどの安全なアプリケーションのふりをして、ユーザのOAuth認証を受けてから、そのユーザになりすましてマルウェア(コンピュータウィルスのような悪意をもったプログラム)をダウンロードするページへのリンクをTwitterでつぶやいたり、SNSサービスの掲示板に書き込むような行為を行うこともできたりします。
それで被害にあった側からみると、そのユーザが加害者にしかみえず、ユーザが、「悪意のあるWebサービスが勝手にやった」と言い訳しても、OAuthで許可をあたえてしまっている以上・・どうしようもない。
そんなリスクを知ったうえで注意して使うべきです。
二段階認証とか「ロボットでない」確認とか
不正ログインを防ぐ手段として以下もあります。
- 二段階認証
- reCAPTCHA(リキャプチャ)の追加
二段階認証は、パスワード認証後にメールかSMS(電話番号で送るショートメッセージ)などでランダムに生成した数字などを送り、それを入力させる方法です。
最近、よくありますよね。
もうひとつ「reCAPTCHA」は「私はロボットではありません」というチェックボックスを追加して、それにチェックをつけさせることで、bot(ここでは自動的にログインを試みるプログラムのこと)ではないことを判別します。
完璧ではないにしても、一定の効果は見込めるので、設計時点で検討はしたほうがいいかなと、個人的におもいます。
各認証に共通する重要ポイント:パスワードの強度
いろいろな認証の種類について書いてきましたが、以下の4つに共通して非常な大事なポイントがもうひとつあります。
- ベーシック認証(基本認証)
- ダイジェスト認証
- セッションベースの認証
- トークンベースの認証
それは・・・ログインするときのパスワードの強度です。
いくら、裏で脆弱性を発生させないように頑張っても、パスワードがいいかげんで、だれでもログインできてしまうようでは、何の役にも立ちません。
いろいろポイントはあるようですが、脆弱性のあるパスワードをシステム提供者側ではじくようにするのが大切なのかなと思います。
その例として。
- 過去に漏えいが確認されたパスワード
- 辞書に含まれる言葉
- 文字の繰り返しや順番で用いること
- - 「aaaaaaaa」といった繰り返しや「1234abcd」のようなアルファベットや数字の順番での使用
- サービス名、ユーザー名など文脈から特定可能な単語。以下例
- - 「QRXYZ」というサービス名のログイン認証時に、「QRXYZ」という語をパスワードに含ませない
- - ユーザー名「mtanaka」の場合、パスワードに「mtanaka」を含ませない
- スペースは入力可能にする
などがとりあげられていることが多いです。
当然ながら、以下のダメパスワードランキングにのっているものとかも、許可しないようにはじいたほうがいいです。
個人的には、さらに、ここに「つかいまわしのパスワード」も含めたほうがいいと思っています。
同一IDで同じパスワードを何回も使うのを禁止するパターンと、だれかが一回でも使ったパスワードは「NGパスワードリスト」に加えて、他の人が使えないようにするパターンがありますが、個人的には後者にすべきと思ってます。
当然ながら、「アカウントロック」は必須です。
ユーザIDごとに、パスワード間違いの回数が上限値を超えるとアカウントをロックしてして、対象利用者と管理者にメール等で報告する・・みたいな感じです。
解除は一定時間経過もしくは管理者による解除。
失敗回数については、10回程度というのが一般的ですかね。
でも、それだけではプログラムを使った攻撃とかを完全に防ぐのは難しく、これについても「類推しやすいパスワード」をいかに使わせないようにするか・・にかかります。
パスワードの定期的な変更でパスワード強度が下がると意味がない
パスワードを強固にすることの重要性が意識されはじめたことによって、昔は常識のように言われていた「定期的にパスワードを変更させる」のは、今では間違いだと180度反対になっています。
これまでは、パスワードの定期的な変更が推奨されていましたが、2017年に、米国国立標準技術研究所(NIST)からガイドラインとして、サービスを提供する側がパスワードの定期的な変更を要求すべきではない旨が示されたところです(※1)。また、日本においても、内閣サイバーセキュリティセンター(NISC)から、パスワードを定期変更する必要はなく、流出時に速やかに変更する旨が示されています(※2)
となってます。
理由は、パスワードを定期的に変更させられるようになると、「複雑なパスワードを作って覚えておく」よりも、「変更しても覚えていられる簡単なパスワード」を作りがちであるから・・です。
いくら、頻繁にパスワードを変更していても、それが覚えられないなんて理由で、単純なパターンの使いまわしになっていては本末転倒です。
これに関しては、僕も激しく同意です。
今回はこんなところで。
ではでは。