セッションの安全な管理

はじめに
Webアプリケーションでは、現在アクセスしているユーザーの状態を管理するために「セッション」と呼ばれる仕組みを利用しています。本記事では、その「セッション」を安全に管理するための基礎を改めて説明します。
セッションとは
セッションとは、Webアプリケーション内で現在アクセスしているユーザーの状態を管理するために広く使われている仕組みです。
例えばECサイトの場合であれば、今ショッピングカートに何が入っているのか、ログインしているのかどうか、ログインしているのであればなんというユーザーIDなのかなど、Webアプリケーションを利用しているユーザーの状態に関連する情報が多数発生します。この「ユーザーの状態」全てを毎回ブラウザーとサーバー間で通信するのではなく、状態に関連する情報はサーバー側で記憶しておき、ブラウザーとサーバーの間ではセッションを示すID、つまりセッションIDのみをCookieとして送受信するという方法がWebアプリケーションではよく行われています。

セッションの管理
セッションは、ユーザーに固有の状態を管理するための仕組みですので、仮にセッションIDの漏えいが発生してしまうと、第三者が他のユーザーの状態を知ってしまったり、他のユーザーになりすましたりといったことが行えてしまいます。
ですので、セッションIDは安全に管理されなければなりません。以降では、セッションIDを安全に管理するための注意点を順に説明します。
推測しやすいセッションIDを使用しない
まず、大前提として推測しやすいセッションIDは絶対に使用してはいけません。
Webアプリケーションでは、Webブラウザーから送られてきたセッションIDによってアクセス元を識別しそのユーザーに紐づく状態を管理します。ですので、他のユーザーのセッションIDが推測可能な状況では、攻撃者はセッションIDを推測することで第三者になりすますことができてしまいます。

現在使われているWebアプリケーション開発用のフレームワークでは、セッションIDを生成する機能が標準で備わっていることが一般的ですので、推測されにくいセッションIDを自分で生成するのではなく、フレームワークの機能を積極的に利用するほうが良いでしょう。
ログアウト時にはセッションを破棄する
ユーザーのログアウト時には、セッションIDをあらわすCookieを削除するだけでなく、サーバー上のセッションに関連するデータも破棄するようにします。
以下に、PHPにおけるセッション破棄のコード例をPHPのマニュアルページから引用して掲載します。
<?php
// セッションの初期化
// session_name("something")を使用している場合は特にこれを忘れないように!
session_start();
// セッション変数を全て解除する
$_SESSION = array();
// セッションを切断するにはセッションクッキーも削除する。
// Note: セッション情報だけでなくセッションを破壊する。
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000,
$params["path"], $params["domain"],
$params["secure"], $params["httponly"]
);
}
// 最終的に、セッションを破壊する
session_destroy();
?>
まず、session_start 関数によりブラウザーからCookieを使って送られてきたセッションIDを受け取っています。
つぎに、7行目でセッション変数に空配列をセットすることで、現在のセッションで保存されているデータを全て解除しています。
12-15行目でセッションIDのCookieを無効にし、20行目で最後にsession_destroy関数を呼び出しセッションの破棄を行っています。
重要な処理の前には認証を再度実施する
例えば、住所氏名やパスワードのような重要な設定情報の変更時や、ECサイトでの決済時など、アプリケーション内で重要な処理を行う際には、すでにログイン済みのユーザーであっても処理の前にもう一度認証を行うようにします。
これによって、セッションIDの漏えいなどで万が一アカウントが乗っ取られている場合でも、もう一度認証を求めることによって重要な処理を行わせないようにできます。
認証後にはセッションIDを再生成する
ログイン処理や先述した再認証などでは、認証が正常に行われたタイミングでセッションIDを再生成するようにします。セッションIDを再生成したあとは、古いセッションIDは無効にします。
セッションIDを再生成し古いセッションIDを無効にすることで、万が一セッションIDが漏えいしている場合であっても、その漏えいしているセッションIDを無効にすることができます。
重要なWebアプリケーションでは一定時間ごとの再認証も検討する
重要な情報を扱うアプリケーションでは、操作の有無に関わらず一定時間ごとに再認証を求めることも検討します。
何かしらの理由でセッションIDが漏えいしてしまうと、多要素認証の導入されているシステムであっても認証が求められず、アカウントが乗っ取られてしまいます。そこで、一定時間ごとに認証を行いセッションIDを再発行することで、万が一セッションIDが漏えいしている場合であっても、アカウント乗っ取りの被害を最小化することができます。
もちろん、再認証によってセッションIDを再生成する場合には、古いセッションIDを無効にすることを忘れてはいけません。
セッションCookieの取り扱い
ここまで、セッションIDの管理について説明しましたが、アプリケーション内での扱いが適切であったとしても、実際にセッションIDを扱うCookieにおいて指定される属性に不備があると、セッションIDの漏えいなどセキュリティ上の問題が発生する可能性があります。
ですので、セッションIDを扱うCookieでは適切に各種属性を設定する必要があります。
HTTPレスポンスヘッダーでセッションIDを示すCookieを設定する例を以下に示します。
Set-Cookie: session=48dd7f15fb32...; path=/; httponly; secure; sameSite=Lax;
まず、セッションIDのCookieにはdomain属性は与えないようにします。domain属性がない場合はCookieが発行されたドメインでのみそのCookieが有効となりますが、domain属性が与えられた場合には、domain属性で指定されたドメインのサブドメインもCookieの有効範囲に含まれます。ですので、よほどの理由がない限りはdomain属性は指定しないようにします。
つぎにpath属性ですが、path属性を指定するとURL内のパスに応じてブラウザーからサーバーへのCookieの送信を制限することができます。ただし、path属性でパスを指定しても、セキュリティ上の効果はないことが知られていますので、セキュリティの保護を目的としてpath属性を使うことはできません。
セッションIDのCookieでは、httponly属性は必ず指定することが望ましいでしょう。httponly属性を指定すると、JavaScriptから document.cookie というプロパティを通じてそのCookieの情報を読むことができなくなります。ですので、もしWebアプリケーションにクロスサイト・スクリプティングが存在した場合でも、攻撃者の作成したJavaScriptによってセッションIDを盗まれるリスクを下げることができます。ですので、セッションIDのCookieには必ずhttponly属性を付けるようにしてください。
secure属性も必ず与えるようにします。Cookiにsecure属性をつけることで、そのCookieはHTTPSの場合にのみブラウザーからサーバーへと送られることになります。現在のWebアプリケーションはHTTPではなくHTTPSで提供されることが当たり前になっていますので、HTTPでのセッション情報の漏えいを防ぐため必ず指定します。
次にsameSite属性です。sameSite属性としてLaxやStrictを指定すると、他のサイトを経由しての form のPOSTなどでCookieが送信されないようになるため、クロスサイト・リクエスト・フォージェリの脆弱性が存在した場合でもリスクを軽減することができます。
sameSite属性としてNoneを与えた場合には、他のサイトからのPOSTでもCookieが送信されるため、クロスサイト・リクエスト・フォージェリの脆弱性が存在した場合にはリスクが高まります。
最後にexpires属性です。expires属性が指定されていないCookieはブラウザー終了時に破棄されますが、expires属性が指定されているCookieはブラウザー終了後もそのCookieが保存され、再度ブラウザーを起動させたときには引き続きそのCookieが有効になっています。
ブラウザー終了後もログイン状態を継続するといった用途の場合には、ログイン状態を維持するのに妥当な期間をこのexpires属性に指定します。
Cookieの属性としては以上になりますが、これらの属性に加えてセッションIDを表すCookieの名前を __Secure- または __Host- で始まるものにすることで、ブラウザーはそのCookieの取り扱いをより安全側に制限するため、潜在的な攻撃などに対して保険的な対策となり得ます。
Set-Cookie: __Secure-session=48dd7f15fb32...; path=/; httponly; secure; sameSite=Lax;
Set-Cookie: __Host-session=48dd7f15fb32...; path=/; httponly; secure; sameSite=Lax;
__Secure- で始まる名前がつけられているCookieは、Set-CookieレスポンスヘッダーがHTTPSで提供され、secure属性がつけられているときにのみ機能します。
__Host- で始まる名前がつけられているCookieは、HTTPSであること、secure属性がついていることに加えて、domain属性が与えられていないこと、path属性が / であることが要求されます。
まとめ
本記事では、Webアプリケーションにおけるセッションの役割とセッションIDの保護について解説をしました。
多くのWebアプリケーション用フレームワークでは、セッションを管理するための機能があらかじめ準備されていますが、セッションとはどのように機能しているのか、どういった使い方をすれば安全になるのかを理解しておくことで、よりWebアプリケーションを安全なものにすることができます。
本記事がWebアプリケーションのセキュリティを理解するための一助となれば幸いです。
下記表の左側にはWebアプリケーションで実装されるそれぞれの機能、表の右側にはその機能を実装するときに発生しがちな代表的な脆弱性や攻撃手法を掲載しています。
どのような機能の実装時にどのような脆弱性を作りこんでしまうのかの参考にしてください。
| 実装する機能 | 発生する脆弱性や関連する攻撃手法 | |
|---|---|---|
| ログイン処理 | 認証機能の突破、セッションに関連する問題 | |
| ユーザーごとの機能 | 認可制御の問題、強制ブラウズ | |
| データの 入出力 |
全体 | パラメータ操作、コードインジェクション |
| HTMLの出力 | クロスサイト・スクリプティング(XSS) | |
| データベースへの アクセス |
SQLインジェクション | |
| ファイルへのアクセス | ディレクトリトラバーサル | |
| 外部コマンドの実行 | OSコマンドインジェクション | |
| サーバー上で状態が変化する処理 | クロスサイト・リクエスト・フォージェリ(CSRF) | |
| エラー処理 | エラーメッセージによる情報漏えい | |
| サーバーからのリクエスト発行 | サーバーサイド・リクエスト・フォージェリ(SSRF) | |
| 正規表現の使用時 | 正規表現によるDoS(ReDoS) | |
| アプリケーション全体 | レースコンディション、バックドアやデバッグモードの残存 | |

