2013年2月15日金曜日

ログアウト機能の目的と実現方法

このエントリでは、Webアプリケーションにおけるログアウト機能に関連して、その目的と実現方法について説明します。

議論の前提

このエントリでは、認証方式として、いわゆるフォーム認証を前提としています。フォーム認証は俗な言い方かもしれませんが、HTMLフォームでIDとパスワードの入力フォームを作成し、その入力値をアプリケーション側で検証する認証方式のことです。IDとパスワードの入力は最初の1回ですませたいため、通常はCookieを用いて認証状態を保持します。ログアウト機能とは、保持された認証状態を破棄して、認証していない状態に戻すことです。

Cookieを用いた認証状態保持

前述のように、認証状態の保持にはCookieを用いることが一般的ですが、Cookieに auth=1 とか、userid=tokumaru などのように、ログイン状態を「そのまま」Cookieに保持すると脆弱性になります。これについては、以前のエントリ「CookieにログインIDを保存してはいけない」にて説明しました。
このため、通常はセッション変数にログイン状態を記録し、CookieにはセッションIDのみを保存します。セッション変数であれば、外部から書き換えることは不可能なので、Cookieに生の値を入れる方法に比べて安全です。

ログアウト機能は本当に必要なのか?

ここで、Webアプリケーションにおいて、本当にログアウト機能が必要なのかを検討します。
仮に、ログイン機能のあるWebアプリケーションにログアウト機能がない、あるいはログアウト機能が不完全である場合、脆弱性診断業者は、この状態を脆弱性と指摘しますが、通常は「低危険度の脆弱性」と判定すると思います。なぜ、低危険度なのでしょうか。

実は私達は、ログアウト機能のない認証方式を知っています。それはHTTP認証(BASIC認証やダイジェスト認証の総称)です。HTTP認証の場合、サーバー側で認証状態は保持せずにリクエスト毎に認証するので、そもそもログアウトという概念がない(認証状態を保存しないので破棄の必要がない)のですが、利用者が入力したIDとパスワードをブラウザが一時的に保存するため、外見上は「ログイン状態が保持されている」ように見えます。
HTTP認証はログアウトができないので不便だという意見はよく目にしますし、それを克服するため、401応答ヘッダを返すことでHTTP認証のログアウトを実現するという例も見かけます。しかし、一般的には、HTTP認証はログアウトができない(ブラウザがIDとパスワードを保持したままになる)ので、以下のような「運用で対処」している場合が多いでしょう。

  • ブラウザを終了する(HTTP認証のID・パスワードは消える)
  • ブラウザの機能によりHTTP認証のID・パスワードを消去する

ブラウザによる認証情報消去機能の例として、Firefoxの画面を以下に紹介します。以下は「最近の履歴を消去」メニューにより表示されるダイアログです。


ここで「現在のログイン情報」にチェックして「今すく消去」ボタンをクリックすると、HTTP認証のIDとパスワードが消去され、見かけ上はログアウトしたように見えます。
これと類似のことは、フォーム認証でもできます。セッションCookie(Expires指定のないCookie)はブラウザの終了と共に消えますし、ブラウザの機能によりCookieを削除することもできます。すると、利用者からは、認証していない状態、すなわちログアウトした状態に見えます。

以上のように、我々は「ログアウト機能のない世界」を知っており、一応それを受け入れていることになります。もしもHTTP認証にログアウト機能(アプリケーションからHTTP認証情報を破棄する機能)がないことが本質的に危険であれば、たとえばJavaScriptの機能としてHTTP認証情報を破棄するメソッドが実装されてもよさそうなものですが、そのような機能はありません。

HTTP認証とフォーム認証では、状態保持の場所が違う

ここで、HTTP認証とフォーム認証では、状態保持の場所が異なることを確認します。
HTTP認証は、認証状態を保持しておらず、リクエスト毎に認証処理が動きますが、利便性のため、一度入力したIDとパスワードはブラウザがキャッシュしています。
一方、フォーム認証の一般的な実装では、認証結果をセッション変数に記憶します。セッション変数はサーバー側で保存する場合が多いので、結果として、認証状態はサーバー側で保持していることになります。以上の内容を下表に整理します。

保持する内容 保存場所
BASIC認証 IDとパスワード ブラウザ
フォーム認証 認証結果 サーバー

すなわち、フォーム認証の場合、セッションIDのクッキーを消しただけでは、サーバー側に認証状態は残ったままということになります。

ログアウト機能がないと困ること

先に、HTTP認証ではアプリケーション側でのログアウト機能が一般的でなく、我々はそれを受け入れていると説明しましたが、フォーム認証も含め、ログアウト機能がないことで困ることを検討します。

まず問題になるのは、共有PCを使っている場合や、離席中のパソコンを勝手に使われるケースです。この場合、ログインしたままのアプリケーションがあれば、それを勝手に使われ、なりすましができてしまいます。

これに対しては、以下の回避策があります。
  • 共有PCでは認証機能のあるアプリケーションを使わない
  • 共有PC使用後はブラウザを閉じる
  • アプリケーションのセッションタイムアウト時間を短くする。セッションタイムアウトすると認証情報も破棄される
  • 離席中のPCを使われること自体が重大な問題なので、離席時にはPCをロックする
  • 同じく、離席中はノートPCを持ち歩く
次に検討するのは、クロスサイト・スクリプティング(XSS)やクロスサイト・リクエストフォージェリ(CSRF)の回避策としてのログアウト機能です。これら受動的攻撃は、ログアウト状態であれば、セッションハイジャックやCSRFによる重要な機能の悪用は避けることができます。

最後に、仮に通信路上でセッションIDが盗聴された場合でも、ログアウトしてしまえば、それ以降の悪用を避けることができます。セッションIDが盗聴されるシナリオでは、ログイン時のIDとパスワードも盗聴されるとも考えられますが、以下のようなサイトであれば、「IDとパスワードは盗聴されないが、セッションIDは盗聴される」という状況は起こりえます。
  • 認証フォームはSSLでIDとパスワードを暗号化して送信している
  • 認証後のページは平文のHTTPであり、セッションIDも平文送信される
このようなサイトの場合、利用者がログアウトすると、その後のセッションハイジャックは止めることができます。

ログアウト機能の効用のまとめ

以上に見てきたように、ログアウト機能は本質的に何かを解決する機能ではありません。何か悪いことが起こった際に、被害を軽減するものでしかありません。下表に、ログアウト機能による被害権限の内容と、対応する根本的解説策をまとめました。

ログアウトによる被害軽減 根本的解決策
離席中のなりすましの防止 離席中はPCをロックする、あるいはノートPCを持って移動する
共有PCによるなりすましの防止 共有PCではログインしない、あるいは利用後にブラウザを閉じる
XSS被害の軽減 XSS脆弱性をなくす
CSRF被害の軽減 CSRF脆弱性をなくす
セッションID盗聴の被害軽減 サイト全体をSSLで暗号化する

このため、HTTP認証でログアウト機能がないことも、「不便だが本質的ではないので許容しよう」ということになっているのでしょう。

ログアウト機能の実装法

ここからは、フォーム認証におけるログアウト機能の実装方法について説明します。
認証状態はセッション変数に保持されているので、以下のいずれかにより、確実に認証状態を破棄することができます。
  • セッション自体を破棄する
  • 認証状態を保持するセッション変数に「ログインしていない」ことを示す値を代入する
PHPの場合、セッションの破棄は以下で実現出来ます。
session_destroy();
しかし、このままだとセッション変数の値までは破棄されません。このため、ログアウト処理のページで、ログアウト機能以外にも実行する場合は、以下のようにセッション変数の破棄も行うと良いでしょう。
session_start();
$_SESSION = array();
session_destroy();
個人的には、ログアウトページでログアウト以外のことをするシナリオは想像がつきませんが、絶対にないとは言い切れないので、保険的にセッション変数の破棄もやっておくとよいということです。

また、ショッピングサイトなどで、ログアウトはするがセッションは破棄したくないという場合も有り得ます。その場合は、下記のように、認証状態のみをクリアします。
$_SESSION['userid'] = false;

セッションIDのCookieを破棄する必要はあるか

PHPのマニュアルのsession_destroy()関数の説明には、ログアウト時にはセッションIDのクッキーを削除しなければならない(原文は"the session cookie must be deleted")とあります。
セッション ID の受け渡しに クッキーが使用されている場合(デフォルト)には、セッションクッキーも 削除されなければなりません。
http://www.php.net/manual/ja/function.session-destroy.phpより引用
しかし、私は、セッションIDのクッキー(PHPのデフォルトはPHPSESSID)は削除する必要はないと考えます。その理由は、セッションが破棄された状態では、セッションIDは単なる乱数であり、攻撃の余地はないからです。

これはホテルのカードキーに例えることができます。最近のホテルはカードキーを採用するところが増えていますが、カードキーにも二種類あり、部屋ごとのカードを使いまわす場合と、チェックアウトのたびにカードキーをリセットして、別のカードキーを使う場合があります。
後者の場合、チェックアウト済みのカードキーには価値がないので、顧客が記念に持ち帰ってもよいところも多いですね。
セッション変数はホテルの部屋に例えることができます。部屋にアクセスするにはカードキー(セッションID)が必要です。顧客は滞在中カードキーさえあればいつでも部屋(セッション変数)にアクセスできますが、チェックアウト(ログアウト)すると、部屋にアクセスできなくなります。入室できないカードキーには意味が無いので、持ち帰っても差し支えないというわけです。

セッションIDには、記念品としての意味はありませんが、認証状態を破棄するという意味においては、ブラウザ側に残っていても不具合はありません。これが、「ログアウト時にセッションIDのCookieを破棄する必要はない」という根拠になります。

セッションIDのCookieを削除したい理由

とはいえ、セッションIDのCookieを削除してはいけない理由もないので、場合によっては削除してもよいでしょう。セッションIDのCookieを削除したくなる理由をいくつか考えてみました。他にもあればご指摘下さい。

  • 不要なものが出続けるのはなんとなく気持ち悪い
  • リクエストヘッダが不要なCookieのサイズ分大きくなるのは非効率である
  • プライバシーポリシーにおいて、Cookieはログイン中のみ使用すると約束している
  • ログアウト後もCookieが送信されることにより、トラッキング等の「あらぬ疑い」をかけられたくない

私は、セキュリティコンサルタントとしては珍しく(?)、余計なこと(保険的対策としても効果の薄いもの)をするのは嫌いなので、「Cookieが残るのは許容してもいい」という意見ですが、Cookieを削除すべきでないとまでは思いません。

脆弱性診断の実務として、ログアウト後にセッションIDのCookieが残ることを指摘する業者はあるかもしれませんが、指摘したとしても「情報」(脆弱性とまでは言えないが一応ご報告)というレベルだと思います。


まとめ

  • ログアウト機能の目的は、セッションハイジャックなどに対する保険的対策である
  • ログアウト機能の実現方法は、セッションを破棄するか、認証状態を保持するセッション変数をクリアすることである
  • ログアウト時にセッションIDのCookieを削除する必要はないが、別の理由があれば削除してもよい

0 件のコメント:

コメントを投稿

フォロワー