セッション管理
HTTP は一般にステートレスプロトコルと呼ばれており、これは一連の個別の HTTP リクエストを関連付けるための、組み込みの仕組みを提供していないということです。これは、クライアント固有の状態を維持したいウェブサイトにとっては問題となります。例えば、あるサイトでは、ユーザーがライトテーマかダークテーマかを選択できるようにしている場合があり、その選択は、そのサイトへのその後のリクエストを通じて維持されるべき状態を表すことになります。これに対応するためには、ウェブサイトは単一のクライアントからの一連のリクエストを「セッション」として扱い、このセッションを永続的な状態と関連付ける必要があります。
ウェブサイトがユーザーを認証し、特定のデータや操作へのアクセス権を付与する機能を持っている場合、通常、認証済みユーザーの識別情報を、クライアントからの複数のリクエストにわたって維持される状態として扱う必要があります。しかし、テーマの選択とは異なり、サイト上の認証済みユーザーの識別情報は、攻撃者にとって特に価値の高い状態を表すため、サイト作成者は認証済みユーザー向けのセッション管理ソリューションを実装する際、細心の注意を払わなければなりません。
セッション管理において最も一般的なモデルは、集中型セッション管理であり、このモデルではユーザーのセッション状態がサーバーに格納されます。 特定のウェブアプリケーションアーキテクチャにおいて有用な別のモデルとして、分散型セッション管理(JWT サーバートークンと呼ばれることもあります)があります。このモデルでは、クライアントは、サーバーによってデジタル署名されたオブジェクトとしてセッション状態を保存します。
以下の節では、まず集中型モデルについて説明し、次に想定される攻撃や実装上の考慮事項について述べます。 その後、分散型モデルについて説明し、両者を比較した上で、それぞれの手法をどの場合に使用するべきかについて考察します。
集中型セッション管理
集中型セッション管理では、ユーザーのセッション状態はサーバーに保持されます。
-
ユーザーが認証されると、サーバーはその状態を記録し、セッション ID を生成して、この状態と関連付けます。サーバーはセッション ID のコピーをクライアントに返します。クライアントはセッション ID を格納します。
-
クライアントがサーバーにリクエストを送信する際、その ID を記載します。サーバーはこの ID を使用してユーザーのセッション状態を参照し、クライアントがすることができる操作を決定します。
攻撃
セッション管理を対象とする場合、攻撃者の目的は、ウェブサイトの認証システムそのものを侵害することなく、正当なユーザーになりすますことにあります。この章では、攻撃者がこれを実現するための主な2つの方法、すなわちセッションハイジャックとセッション固定化について説明します。
セッションハイジャック
セッションハイジャックとは、攻撃者が正当なユーザーのセッションを引き継ぐ攻撃の総称です。これには代表的な手法がいくつかありますが、すべて認証済みユーザーのセッション ID 値にアクセスすることを伴います。
セッション ID の傍受
攻撃者は、中間者攻撃 (MITM) において、クライアントとサーバーの間で送信中のセッション ID を盗み出すことが可能です。
この対策として、ウェブサイトを TLS 経由で提供することが挙げられます。
セッション ID の予測
攻撃者が、正当なユーザーに割り当てられたセッション ID の値を予測、推測、または総当たり攻撃によって特定できた場合、その ID を使用して、ユーザーのアカウントを盗むことなく、そのユーザーになりすますことが可能になります。
この対策は、セッション ID の値が十分に長く、かつ予測不可能なほどランダムであるように確実に実現することです。
クライアントへの攻撃
攻撃者は、例えばクロスサイトスクリプティング (XSS) 攻撃のように、対象となるウェブサイトのコンテキスト内でユーザーのブラウザーに自身のコードを実行させることができれば、セッションを乗っ取ることができます。
セッション ID が JavaScript から利用できる場合(例えば、ローカルストレージに格納されているためなど)、攻撃者はその ID を盗み出し、それを利用してユーザーになりすますことができます。
この攻撃に対する防御策としては、通常の XSS に対する防御策を実装することが挙げられます。
セッション固定化
セッション固定化攻撃では、攻撃者はセッション ID の値を選択し、その値を対象とするユーザーのセッション ID として使用させるようウェブサイトに仕向けます。攻撃者は ID を把握しているため、そのユーザーになりすますことが可能になります。この攻撃では、攻撃者は最初から ID を知っているため、ID を盗み出す必要はありません。
例えば、対象のウェブサイトが、クライアントからのリクエストにセッション ID が URL 引数として含まれていることを想定しているとします。また、当初、対象のユーザーは対象のウェブサイトにアカウントを持っているものの、ログインしていない状態であるとします。
攻撃者はセッション ID の値をでっち上げ、ユーザーにリンクを含むメールを送信します。そのメールには、ユーザーがリンクをクリックしたくなるような、もっともらしい理由が記載されています。そのリンクは標的のウェブサイトへのものであり、URL 引数として攻撃者が生成したセッション ID が含まれています。
https://target-website.example.org/login?session=攻撃者が生成したID
ユーザーがリンクをクリックすると、セッション ID がウェブサイトに送信されます。その後、ユーザーがログインし、ウェブサイトがログインしたユーザーに対してこのセッション ID を再利用した場合、攻撃者はその値をすでに把握しているため、ユーザーになりすますことが可能になります。
もしウェブサイトが、セッション ID を送信するために URL 引数ではなくクッキーを使用している場合、この攻撃はより困難になります。攻撃者は、標的となるウェブサイトに対して XSS 攻撃を実行できる必要があります。これが、セッション ID の保存や通信において、クッキーが URL 引数よりもはるかに優れた手法である理由の一つです。
しかし、セッション固定化に対する主な防御策は、ユーザーがログインするたびに、サーバーが常に新しいセッション ID を生成し、既存の値を無効にすることです。
セッション ID 値
セッション ID は次のようにすべきです。
-
推測や予測攻撃から保護するのに十分なエントロピーが含まれていること。OWASP のセッション管理ガイド(英語)では、少なくとも 64 ビットのエントロピーを推奨しています。
-
サーバー側の識別子としてのみ機能し、それ以外の意味を持たないこと。つまり、ユーザーやそのアカウントに関する情報は一切含まれてはいけません。
可能であれば、ウェブサイトでは信頼できるフレームワークやライブラリーを使用してセッション ID を生成しましょう。
セッション ID ストレージ
クライアントはセッション識別子を安全に格納できる必要があります。そうすることで、攻撃者がその値にアクセスしてセッションを乗っ取ることを防ぐことができます。
セッション識別子を格納するために主な方法は、ローカルストレージとクッキーの 2 つです。
推奨される選択肢はクッキーです。なぜなら、ウェブサイトは HttpOnly 属性を設定することで、JavaScript からクッキーにアクセスできないようにできるからです。この属性が設定されている場合、XSS 攻撃が成功したとしても、悪意のあるコードはセッション ID 自体にアクセスできません(例えば、攻撃者に送信するなど)。これは、他のクライアント側ストレージ方法に比べて明らかな利点となります。
ただし、これは完全な保護策ではないことに注意してください。悪意のあるコードは、ユーザーのブラウザーからセッション ID を含む HTTP リクエストを対象サーバーに対して送信することが可能であり、その結果、そのサイト上でユーザーの権限を付与されてしまうからです。
セッション ID 転送
セッション ID がローカルストレージに格納されている場合、クライアントはその ID を明示的に読み取り、送信するリクエストに含める必要があります。
セッション ID がクッキーに含まれている場合、クライアントがリクエストを送信する際に自動的に送信されます。なお、どのリクエストにクッキーを含めるかを制御する属性もあります。
保護された転送
Secure 属性が設定されている場合、そのクッキーは暗号化されていない接続では送信されません。これは、転送中にセッション識別子が盗み出される 中間者攻撃 (MITM) に対する防御策です。
クッキーの送信先の制御
Domain および Path 属性は、Cookie が送信される URL を制御するものであり、サイトの要件に応じて、可能な限り制限の厳しい値に設定する必要があります。
デフォルトで、クッキーはそれを設定したホストに対してのみ送信されます。例えば、https://login.example.org から設定されたクッキーは、https://products.example.org や https://example.org へのリクエストには含まれません。この制限を緩和する必要がある場合は、Domain 属性を使用して設定できますが、完全に異なるサイトへのリクエストにクッキーを含めることはできません。例えば、https://example.org からクッキーが設定された場合、それを https://example.com に送信するように設定することはできません。
CSRF の考慮
ウェブサイトがセッション識別子の送信にクッキーを使用する場合、クロスサイトリクエストフォージェリー (CSRF) 攻撃に対する対策を実装しなければならない。ここでのリスクは、ブラウザーが攻撃者のサイトから対象ウェブサイトへのリクエストにクッキーを記載してしまう可能性があり、そのクッキーに有効なセッション ID が含まれている場合、サーバーはそのリクエストを正当なユーザーからのものとして扱い、攻撃者の要求を実行してしまうことです。
当社の CSRF ガイドでは、推奨される防御策についてこちらで説明しています。なお、SameSite クッキー属性を設定することは、完全な防御策の一部に過ぎない点にご注意ください。
セッションの生存期間
ユーザーが再認証を行う必要が生じるまでのセッションの有効期間を決定することは、セキュリティと利便性のバランスを考慮した判断となります。セッションの有効期間が長ければ長いほど、攻撃者がセッションを乗っ取ったり、盗んだセッション ID を使用したりする時間が長くなります。一方で、再認証が必要になることは、ユーザーにとって煩わしさの原因となります。
ウェブサイトは、Expires および Max-Age といったクッキー属性を使用して、クッキーの有効期限を制御することができます。これらの属性のどちらか一方でも設定されている場合、そのクッキーは持続的ななクッキーとなります。つまり、その値は記録されるため、ブラウザーを再起動しても保持されます。
両方の属性が省略された場合、ブラウザーが閉じられた後、ブラウザーはそのクッキーを削除します。OWASP チートシートでは、ウェブサイトはこれらの属性を除外すべきであると推奨しています。しかし、多くのウェブサイトでは、セッション管理のために永続的なクッキーを使用しています。
タイムアウト
OWASP の早見表では、3 種類のセッションタイムアウトについて説明してありますが、これらはすべてセッション管理戦略の一環として実装することが可能です。
-
アイドルタイムアウト:これは、クライアントがサーバーに対して HTTP リクエストを送信していない期間(アイドル状態)が一定時間続いた後にタイムアウトします。タイムアウト後は、ユーザーが再認証を行う必要があります。
-
絶対タイムアウト:これは、アクティビティの有無にかかわらず、特定の時間が経過するとタイムアウトします。タイムアウト後は、ユーザーが再認証を行う必要があります。
-
更新タイムアウト:これは絶対タイムアウトよりも短いタイムアウトであり、非アクティブ状態には依存しません。ただし、タイムアウトが発生すると、サーバーは以下の処理を行います:
- 新しいセッション ID を自動的に生成する
- 古いセッション ID を無効化する
- 新しいセッション ID をクライアントに送信する
これにより、ウェブサイトがユーザーに頻繁に再認証を求めずに、攻撃者がセッション ID を利用できる期間を制限することができます。
これらのタイムアウトはすべてサーバー側で計算すべきであり、セッションの有効期限切れ処理もサーバー側で行う必要があります。利便性の観点からは、クライアント側でもセッション状態をクリアすべきですが、セキュリティの観点からはサーバー側の状態が重要です。
特定のタイムアウト期間は、セッションの機密性や使用状況によって異なります。OWASP の早見表には推奨事項が記載されています。
無効化イベント
状況によっては、ウェブサイトがユーザーのセッションを無効化し、再認証を要求する場合があります。
-
クライアントが、サイト上のユーザー資格情報の変更を試みたり、実際に変更したり、アカウント回復プロセス(例えばパスワードのリセット)を開始したりするなど、リスクの高い操作を試みた場合。
-
サーバー側が、セッション ID が盗まれた可能性があると判断する根拠がある場合です。これには、例えば、新しい IP アドレスや端末からのログインなどが含まれます。
分散型セッション管理
これまで説明してきた「集中型セッション管理」モデルでは、セッション状態はサーバー上のデータベースに保持され、クライアントにはその状態の識別子が指定され、クライアントはリクエストを行う際にその識別子をサーバーに送信します。
これとは対照的なモデルとして、「分散型セッション管理」と呼ばれるものがあります。このモデルでは、セッション状態はクライアント側でデジタル署名されたオブジェクトとして保持されます。こうした署名付きオブジェクトは、通常、JSON Web Tokens (JWT) として表されます。
サーバーがユーザーを認証する際、サーバーは以下の処理を行います。
- ユーザーのセッション状態を表すトークンを作成する。このトークンによって、そのユーザーにどのようなアクセス権限を与えるかが決定される。
- このトークンにデジタル署名を行う。
- 署名済みのトークンをクライアントに返す。
クライアントがリクエストを送信する際、署名付きトークンをサーバーに提示します。サーバーは署名を検証し、セッションの状態に基づいてリクエストの処理方法を決定します。
状態はクライアント側で管理されるため、このモデルは、クライアントが複数の異なるサーバーにリクエストを送信する可能性がある分散アプリケーションで広く採用されています。クライアントは任意のサーバーにトークンを渡します。そのサーバーがトークン発行者による署名を検証できれば、サーバーはリクエストの処理方法を決定できます。つまり、リクエストを処理するサーバーがトークン発行者と直接やりとりする必要はありません。
下記図では、クライアントが2つのサーバーと対話する様子を示しています。
- ユーザーを認証し、トークンを発行する「トークンサーバー」。
- トークンの有効性を確認し、適切に認証されたクライアントからのリクエストを処理する「リソースサーバー」。実際のアプリケーションでは、もちろん、複数のリソースサーバーがあり、それぞれがアプリケーションの異なる側面を処理している場合があります。
なお、分散型のセッション管理は集中型モデルよりも複雑になりがちであり、いくつかの脆弱性を生じさせる可能性があります。これについては、次の数節で詳しく説明します。アプリのアーキテクチャ上、分散型が必要ない場合は、一般的に集中型モデルを使用する方が良いでしょう。
トークンストレージ
トークンの格納に関する注意事項は、セッション ID ストレージの場合とほぼ同じです。攻撃者がトークンを盗み出せれば、ユーザーのセッションを乗っ取ることができてしまいます。そのため、ウェブサイトでは多くの場合、クライアントサイド XSS に対する保護機能を活用するために、トークンを HttpOnly クッキーに格納することを選択します。
分散型でトークンベースのモデルでは、いくつか以下の点に留意する必要があるかもしれません。
-
トークンはセッション ID よりもはるかに大きく、クッキーの最大サイズは 4KB です。
-
分散型アプリケーションが異なる登録可能なドメインのサービスを使用する必要がある場合、クッキーを使用してトークンを格納することはできません。なぜなら、ブラウザーはクッキーを設定したサイトとは異なるサイトに対してクッキーを送信しないからです。例えば、
https://example.comがクッキーを設定した場合、そのクッキーはhttps://example.orgには取得されません。
トークンの検証
分散型セッション管理に対する多くの攻撃は、トークンの検証に焦点を当てています。つまり、リソースサーバーがリクエストの処理方法を決定する前に、トークンおよびそこに含まれるクレームを検証するプロセスです。
例えば、トークンに署名する目的は、攻撃者が既存のトークンを改ざんしたり、標的の権限を付与する独自のトークンを作成したりすることを防ぐことにあります。しかし、JWT 書式では署名を含まないトークンも許容されており、一部の JWT 検証ライブラリーは過去にそのようなトークンを受け入れていました。これは、攻撃者が以下のことを可能にすることを意味します。
- 偽のトークンを作成し、攻撃者に標的のアカウントへのアクセス権を与える
- このトークンには署名が含まれていないことを示す
- そのトークンをリソースサーバーに提示する
リソースサーバーの JWT ライブラリーが署名の存在を必須としていない場合、そのトークンを受け入れることができ、攻撃者にアクセス権を与えてしまうことがあります。
このような攻撃を防ぐためには、リソースサーバーは、使用する JWT ライブラリーが常にトークンの署名を調べるようにする必要があります。
セッションの無効化
無効化イベントに関する解説で、セッション ID が攻撃者に盗まれた可能性を示すイベントが発生した場合、ウェブサイトがユーザーのセッションを無効化し、再認証を強制したい状況があることが分かりました。例えば、ユーザーがパスワードの変更を試みた場合、サイトは当該ユーザーのすべての現在のセッションを無効にする必要があります。
サーバー側でユーザーセッションの状態を管理する集中型モデルでは、サーバーは保存しているセッション状態を削除することで、セッションを無効にすることができます。
一方、分散型モデルでは、クライアントがセッショントークンを管理し、それ自体が完結した単位となります。つまり、リソースサーバーは、トークンの内容のみに基づいてリクエストの処理方法を決定できる必要があります。そのため、一度発行されたトークンを無効にすることは困難となります。
この問題に対する最も一般的な解決策は、次の通りです。
-
クライアントがリソースサーバーにアクセスするために使用するトークン(「アクセストークン」と呼ばれることもあります)の有効期間を短く設定し、万が一盗まれた場合でも、長期間使用できないようにします。
-
「更新トークン」と呼ばれる新しい種類のトークンを追加します。これは、ユーザーが認証を行った際にクライアントに指定されるものです。このトークンの有効期間は、アクセストークンよりもはるかに長くなっています。
-
有効な更新トークンが表示された場合、トークンサーバーが新しいアクセストークンを発行することができるようにします。
更新エンドポイントは、ユーザーのセッションを無効にするべきかどうかを判断するための、アプリケーション向けの集中管理場所となります。セッションを無効にすることを選択した場合、新しいアクセストークンの発行を拒否することで、その処理が行われます。
フレームワークとライブラリー
セッション管理の詳細をすべて自前で実装するよりも、定評のあるフレームワークやライブラリーを使用し、そこで提供されているセッション管理機能を活用することをお勧めします。
セッション管理チェックリスト
上記の提言をまとめると、以下のようになります。
- アプリのアーキテクチャが許すのであれば、セッション管理には集中型モデルを選択してください。
- セッション ID は、クッキーに保存することを推奨します。その場合は、以下の点に注意してください。
SecureおよびHttpOnly属性を設定してください。- CSRF 対策を実施してください。具体的には、
SameSite属性をLaxまたは、できればStrictに設定するほか、フェッチメタデータや CSRF トークンなどの他の手法も併用してください。
- セッションの有効期限に関するポリシーを定義してください。具体的には、アイドルタイムアウト、絶対タイムアウト、更新タイムアウトなどを設定することを検討してください。
- セッションハイジャックを示す可能性があるイベントが発生した場合は、ユーザーのセッションを無効化してください。
- トークンを使用した分散型セッション管理システムを実装する場合は次のようになります。
- エンドポイントがトークンを正しく検証できるように実現してください。
- 更新トークンの追加や、アクセストークンの有効期間を短くすることを検討してください。
- セッション管理を独自に実装するのではなく、定評のあるセッション管理フレームワークやライブラリーを使用してください。
関連情報
- セッション管理早見表(英語) (OWASP)