atsukanrockのブログ

Microsoft系技術を中心にぼちぼち更新します

ASP.NETで忘れずにやっておくべきこと(2) セッションハイジャック対策

はじめに

本エントリは『ASP.NETで忘れずにやっておくべきこと』シリーズの第2回。「セッションハイジャック対策」について記す。

目的

セッションハイジャック攻撃に対し、十分な防御力を持つASP.NET Webアプリケーションを作成する。

注意

「セッション」ハイジャックという名前からして、セッション状態*1に対する攻撃のみを指すように思える。これを狭義のセッションハイジャックとするなら、本エントリでは、広義のセッションハイジャックについて述べる。広義のセッションハイジャックとは、攻撃者がアプリケーションの正当なユーザーになりすますことを言うものとする。

セッションハイジャックとは?

まず、セッションハイジャック攻撃について、その攻撃方法と一般的な対策法を学ぶ。

最適なテキストは情報処理推進機構:情報セキュリティ:脆弱性対策:安全なウェブサイトの作り方の『安全なウェブサイトの作り方』(PDF)だろう。「1.4 セッション管理の不備」が、セッションハイジャック攻撃に関する項だ。非常に簡潔に、さらに正確さを損なわないよう、記述されている。

さらに具体的な攻撃方法にまで踏み込んでいるのはマネージドセキュリティサービス(NTT Com セキュリティオペレーションセンタ)|NTT Com 法人のお客さまの『Session Adoption in Session Fixation』(PDF)あたりだろうか。ただしこちらは、PHPにフォーカスしたドキュメントとなっている。

フォーム認証を使え

セッションハイジャック対策で最も重要なことを、最初に述べる。ASP.NETでは、ユーザー認証のためにセッション状態を使ってはならない。フォーム認証など、ユーザー認証を目的とする機能を用いなければならない。私が普段、フォーム認証以外のユーザー認証機能を使わないので、以降の記述は、フォーム認証に限定する。また、フォーム認証には、Cookieを使う方法とURLを使う方法があるが、より一般的な、Cookieを使う方法についてのみ記述する。

なぜユーザー認証のためにセッション状態を使ってはならないか。それは、セッション状態はフォーム認証に比べ、攻撃者がなりすませる可能性が高いからだ。以下に、その理由を示す。

まず、ASP.NETのセッション固定対策 - 2010-04-24 - T.Teradaの日記の「Session Adoption」によると、ASP.NETにはSession Adoption脆弱性が存在する(HTTP要求が主張するセッションIDが、ASP.NET自身が発行したセッションIDかどうかを検証せず、受け入れてしまう)そうだ。以下、当該ブログ記事から引用する。

なお、ASP.NETが無効になったセッションIDを再利用するという事実は、ASP.NETにSession Adoptionの問題があることを示唆しています。実際のところ、一定の長さや文字種類の条件を満たすセッションIDであればなんでもASP.NETは受け入れてしまいます*2。

*2:ただし、AdoptionされるのはCookieを使う場合だけで、URLにセッションIDを埋め込む方式を選択した場合には、無効なセッションIDを受け入れない設定であるregenerateExpiredSessionIdが有効になります。このあたりは、HttpSessionState.IsCookieless プロパティ (System.Web.SessionState)で背景を含めて説明されています。

これが本当かどうか、私はテストしていない。さらに、HttpSessionState.IsCookieless プロパティ (System.Web.SessionState)を私が読んだ限りでは、「一定の長さや文字種類の条件を満たすセッションIDであればなんでもASP.NETは受け入れてしまいます」と読み取れる記述は、ないように思えた。ただ、この記述の真偽は、ここでは重要ではない。その理由は後述する。

Session Adoption脆弱性が疑われることに加え、ブラウザ側の脆弱性として、Cookie Monsterがある。これは、古いブラウザでは残されたままになっている脆弱性で、Webアプリケーション側では根本的解決ができない。

以上により、ASP.NETのセッション状態は、Session Adoption+Cookie MonsterによるSession Fixationで、セッションハイジャックされてしまうことになる。

これに対し、フォーム認証は、この方法ではセッションハイジャックされない。なぜなら、フォーム認証には、Session Adoption脆弱性がないからだ。理由は、How To: ASP.NET 2.0 でフォーム認証を保護する方法で示されている。フォーム認証は、攻撃者が生成したCookie値を受け入れず、エラーとする。

つまり、Session Fixationに対する防御力の面で、フォーム認証には穴がないのに対し、セッション状態には穴がある可能性がある。セッション状態のSession Adoption脆弱性について、真偽が重要でないと書いたが、その理由がこれだ。穴がない手段が存在するのに、わざわざ穴があるかもしれない手段を使う理由がない。この理由で私は、真偽の確認作業すらしていないし、するつもりがない。

各「根本的解決」の実装方法

以下で、前述の『安全なウェブサイトの作り方』の「1.4 セッション管理の不備」で説明されている、各「根本的解決」について、ASP.NETでの実装方法を示す。

なお、上でセッション状態に対するフォーム認証の優位性を示したが、フォーム認証が使えるのは、ユーザー認証目的に限定される。セッション単位で保持したい、ユーザー認証以外の目的のデータは、パフォーマンス面の理由で、セッション状態に格納しなければならない。そのため、セッション状態を全く使わないというわけにはいかない。従って以降では、フォーム認証、およびセッション状態の両方について、実装方法を示すことにする。

1) セッションIDを推測が困難なものにする

フォーム認証、セッション状態とも、デフォルトでクリアできる。

2) セッションIDをURLパラメータに格納しないようにする

フォーム認証、セッション状態とも、デフォルトでクリアできる。

3) HTTPS通信で利用するCookieにはsecure属性を加える

フォーム認証、セッション状態とも、ASP.NET構成ファイル(Web.config)で設定することでクリアできる。

フォーム認証
authentication の forms 要素 (ASP.NET 設定スキーマ)のrequireSSL属性に、trueを設定する
セッション状態
httpCookies 要素 (ASP.NET 設定スキーマ)のrequireSSL属性に、trueを設定する
4-1) ログイン成功後に、新しくセッションを開始するようにする

以前にも書いたが、これを実装するには一工夫が必要だ。なぜ一工夫必要なのかについては、以前のエントリを参照されたい。ここでは、以前のエントリのコメントに書いた、ダミーページを使う方法のサンプルコードを示す。

まず、ASP.NET構成ファイルで、次のように承認を構成する。

<location allowOverride="false" path="auth">
    <system.web>
        <authorization>
            <deny users="?" />
        </authorization>
    </system.web>
</location>

この例では、~/authフォルダ以下のファイル*2に対しては、認証済のユーザーしかアクセスできないよう、構成している。

次に、ログインフォームを持つページ(~/login.aspxとする)で、次のようにする。

// ログインに成功した場合のみ、ここに到達することとする

// 次回 HTTP 要求時に、新しいセッション状態が生成されるようにする
Session.Abandon();

// 次回 HTTP 要求時に生成されるセッション状態のセッション ID が、
// 現在のものと異なるものになるようにする(セッション Cookie のクリア)
var sessionStateSection
    = (SessionStateSection)WebConfigurationManager.GetWebApplicationSection(
    "system.web/sessionState");
Response.Cookies.Add(new HttpCookie(sessionStateSection.CookieName, string.Empty));

// フォーム認証 Cookie を HTTP 応答に設定
FormsAuthentication.SetAuthCookie(userID, false);

// ダミーページにリダイレクト
Response.Redirect("~/auth/login_process.aspx");

ここで、セッションCookieのクリアについて少し説明しておく。このコードは、ASP.NET でセッション ID が再利用されるしくみと理由についてで紹介されているのを、少し改善したものだ。詳細についてはリンク先を参照されたい。簡単に言えば、このコードがなければ、せっかくHttpSessionState.Abandon メソッド (System.Web.SessionState)を呼び出しても、次に生成されるセッション状態のセッションIDが、破棄したはずのセッション状態と同じものになってしまう。厳密にはそうならないこともあるが、通常の構成だとそうなってしまう。これではSession Fixationに対する脆弱性が残ったままとなってしまうため、上のコードのように、セッションCookieのクリアが必要になる。(2010/08/02追記)teraccさんからご指摘。ここでの目的「セッションIDを変える」に対し、このコードは適切でない。詳細については、このエントリに対するtaeraccさんのコメント、およびそれに対する私のコメントを参照のこと。

ところで、セッションCookieのクリアは、ViewStateUserKey(詳細についてはASP.NETで忘れずにやっておくべきこと(1) ViewStateUserKeyを使う - 熱燗ロックのブログを参照)も使っている場合には要注意だ。なぜなら、ViewStateUserKeyを使っているページでは、ポストバックしている間(つまり、ビューステートが保存される間)、セッションIDが変わってしまわないようにする必要があるからだ。セッションCookieをクリアすると、その次回のHTTP要求では、セッションIDが変わる。従って、ViewStateUserKeyを使っているページでセッションCookieをクリアしたら、HttpResponse.Redirect メソッド (System.Web)などにより、次回HTTP要求がポストバックでなくなるようにしなければならない。

ちなみに、上のコードではフォーム認証CookieのHTTP応答への設定を1行で行っているが、ユーザーロールなど、追加的なデータをフォーム認証Cookieに格納したい場合、それでは済まない。ここのコードが1行で済まないのに加え、Global.asax(.cs)にもコードを追加する必要がある。詳細については、@IT:連載:プログラミングASP.NET 第19回 フォーム認証を実装したASP.NETアプリケーションなどを参照されたい。なお、このリンク先のコードは、私からすると改善の余地があるが、参考にはなるだろう。

さて、話が横道にそれたが、ダミーページ(~/auth/login_process.aspxとする)では、次のようにする。

// ページにアクセスできている時点で、ユーザー認証済なことが保証される

// (ここで、セッション単位で保持したいデータを、セッション状態に格納する)

// トップページにリダイレクト
Response.Redirect("~/auth/top.aspx");

1つ目のコメントの内容が重要だ。ASP.NET構成ファイルで上のように承認を構成しているので、ダミーページに未認証ユーザーがアクセスすることはできない。従ってダミーページでは、認証に関するコードは一切必要ない。

結局のところ、ダミーページで行っているのは、セッション単位で保持したいデータのセッション状態への格納のみである。これはつまり、もしセッション単位で保持したいデータがないのであれば、ダミーページが必要ないということだ。ダミーページは、必要な場合だけ設けるべきだろう。ログイン後のページに遷移するまでに、1回余計なHTTP通信が必要になるし、FormsAuthentication.RedirectFromLoginPage メソッド (System.Web.Security)のようなことをしようとすると、一工夫必要になる。

4-2) ログイン成功後に、既存のセッションIDとは別に秘密情報を発行し、ページの遷移毎にその値を確認する

フォーム認証Cookieこそが、まさにこの既存のセッションIDとは別の秘密情報である。

デフォルトの防御力

ここで、フォーム認証とセッション状態の、セッションハイジャック攻撃に対するデフォルトの状態での防御力を、表にしておく。『安全なウェブサイトの作り方』で紹介されている3つの攻撃方法に対する、防御力を示す。○は十分な防御力を持つことを、×はそうでないことを表す。

機能 セッションIDの推測 セッションIDの盗用 セッションIDの固定化
フォーム認証 ASP.NET構成ファイルでCookieにsecure属性を付け、HTTPS通信を使えば○
セッション状態 ×*3

おわりに

以上により、ASP.NETでは、セッションハイジャックに対する「根本的解決」を、比較的容易に、全て実装できることが分かった。ただし、以前にも述べたとおり、用意されているたくさんの機能を、適切に組み合わせて使うことが難しい。初めてASP.NETを使う人は、用意されている機能が多すぎて、混乱してしまうのではないか。

このエントリも、かなりの長文になってしまった。なのでここで、繰り返しになるが要約しておく。セッションハイジャック対策で最も重要なのは、「フォーム認証を使え」ということだ。そのうえで、本エントリで紹介した「根本的解決」を実装することで、セッションハイジャックに対して強固な防御力を持つ基盤ができ上がる。後は、スクリプトインジェクションなど、基盤だけでは対処できない攻撃に対し、注意深く対処していくだけだ*4

*1:いわゆるセッションオブジェクト、セッション変数などと呼ばれるもの

*2:ただし、ASP.NETが処理するものに限る

*3:Session Adoption脆弱性があるのであれば

*4:プログラマ全員が適切なコードを書く必要があるので、この点の方が難しいのかもしれないが…