セッションフィクセーション

**セッションフィクセーション**（英語: session fixation、別名:**セッション固定攻撃**）は、システムが他人のセッションID（SID）を固定（特定または設定）することを許可している脆弱性を悪用するエクスプロイトである。攻撃者が事前に手に入れたセッションIDを、ユーザーに強制的に使わせてログインさせ、そのセッションIDを乗っ取ってユーザーになりすまし、サイバー攻撃を行う。ほとんどのセッションフィクセーションはWebベースであり、URL（クエリ文字列）またはPOSTデータからのセッション識別子の受け入れに依存している。

## 攻撃シナリオ
アリスは銀行 `` に口座を持っている。マロリーは、アリスの銀行口座から金を盗むことを企んでいる攻撃者である。アリスはマロリーをある程度信頼しており、マロリーが送ってきたリンクを閲覧する。

### 単純な攻撃シナリオ
単純なシナリオは以下の通りである。

# マロリーは、`` が任意のセッション識別子を受け入れ、クエリ文字列からのセッション識別子も受け入れ、さらにセキュリティ検証がないことを突き止めた。したがって、`` は安全ではない。
# マロリーはアリスに電子メールを送る。「ねえ、これ見て。私たちの銀行に新しい口座概要機能ができたよ。 ``」。マロリーはSIDを `I_WILL_KNOW_THE_SID` に固定しようとしている。
# アリスは興味を持ち、`` を閲覧する。通常のログイン画面が表示されているので、アリスはログインする。
# マロリーは `` を閲覧し、アリスの口座への無制限のアクセス権を手に入れる。

### サーバー生成SIDを使用した攻撃
サーバーが生成したセッション識別子のみを受け入れるサーバーであれば、固定攻撃から安全であるというのは誤解である。これは**誤り**である。

シナリオ：

# マロリーは `` を閲覧し、どのSIDが返されるかを確認する。例えば、サーバーは `Set-Cookie: SID=0D6441FEA4496C2` と応答するかもしれない。
# マロリーはアリスに電子メールを送ることができる。「私たちの銀行のこのクールな新機能を見て。 ``」
# アリスは、固定されたセッション識別子 `SID=0D6441FEA4496C2` でログオンする。
# マロリーは `` を閲覧し、アリスの口座への無制限のアクセス権を手に入れる。

### クロスサブドメインCookieを使用した攻撃
このタイプの攻撃は、ユーザーのブラウザの脆弱性に依存しない点を除けば、クロスサイトCookie攻撃に似ている。むしろ、サブドメインによってワイルドカードCookieが設定され、そのCookieが他のサブドメインに影響を与える可能性があるという事実に依存している。

シナリオ：

# Webサイト `www.example.com` は、信頼できない第三者にサブドメインを割り当てている。
# そのような第三者の一人であり、現在 `evil.example.com` を管理しているマロリーは、アリスを自分のサイトに誘い込む。
# `evil.example.com` への訪問により、アリスのブラウザにドメイン `.example.com` のセッションCookieが設定される。
# アリスが `www.example.com` を訪れると、このCookieがリクエストとともに送信され、アリスはマロリーのCookieによって指定されたセッションを持つことになる。
# ここでアリスがログオンすると、マロリーは彼女のアカウントを使用できる。

この攻撃が完了すると、マロリーはアリスとして `www.example.com` にアクセスできるようになる。

セッション固定攻撃を悪用するためにユーザーのログインは必須ではない。これらの非認証攻撃はクロスサブドメインCookie攻撃に限定されるものではないが、サブドメイン攻撃の影響はこれらの非認証シナリオにも関連している。例えば、マロリーは自分の悪意あるサイトからURLを提供し、非認証シナリオにセッションを固定して、そのターゲットを悪用する技術を使用する可能性がある。これには、非認証シナリオ（フォームや登録など）の悪用だけでなく、確立されたセッションをユーザーに送り込んでログインを完全にバイパスする機能も含まれる。

例えば、マロリーが *www.example.com* 上にユーザー「A1ice」を作成し、そのユーザーでログインして現在有効なセッション識別子を取得したとする。次にマロリーは、*evil.example.com* からのURLでアリスを罠にかけ、そのセッションCookieをアリスのブラウザに固定し（上記のように）、特定のトランザクションを完了させる（あるいは実際にはより広範な用途のために）ために *www.example.com* にリダイレクトする。こうしてマロリーは、元のログインからセッションを「ゴースト化（ghost）」し、*www.example.com* 上で「A1ice」としてデータをスクレイピングしたり操作を実行したりすることができる。もしアリスが騙されて自分のクレジットカードをアカウントに保存してしまった場合、マロリーはそのカードを使って買い物をすることもあり得る。

## 対策
### GET / POST変数からのセッション識別子を受け入れない
URL（クエリ文字列、GET変数）やPOST変数のセッション識別子は、この攻撃を単純化してしまうため推奨されない。GET / POST変数を設定するリンクやフォームを作成するのは容易であるためである。

* ユーザーがアドレスバーから「興味深いリンク」をチャット、フォーラム、コミュニティなどにコピー＆ペーストすることで、SIDが他人に漏洩する。
* SIDが多くの場所（ブラウザの履歴ログ、Webサーバーのログ、プロキシのログなど）に保存される。

注：Cookieはタブ間やポップアップされたブラウザウィンドウ間で共有される。もしシステムが同じドメイン（www.example.com/?code=site1 および www.example.com/?code=site2）へのアクセスを要求する場合、Cookieがタブ間で互いに競合する可能性がある。

この制限を克服するために、URLでセッション識別子を送信する必要があるかもしれない。可能であれば、site1.example.com や site2.example.com を使用して、Cookieのドメイン競合が発生しないようにする。これには追加のSSL証明書によるコストが発生する場合がある。この挙動は、多くのサイトで別のタブを開いて検索結果を並べて比較しようとすることで確認できる。セッションの1つが使用不能になるだろう。GETおよびPOSTにおけるセッション識別子はPHP 8.4で非推奨となり、PHP 9.0で削除される予定である。

#### 本人確認
この攻撃は、ユーザーがログインする際にセッションIDを変更、再生成することで大部分を回避できる。ユーザー固有のすべてのリクエストでユーザーがサイトに認証されている（「ログインしている」）必要がある場合、攻撃者は被害者のログインセッションのIDを知る必要がある。しかし、被害者が固定されたセッションIDを持つリンクを訪れたとき、彼らが自分自身として何か「重要な」ことをするためには、自分のアカウントにログインする必要がある。この時点でセッションIDが変更されれば、攻撃者は匿名のセッションIDを使って「重要な」ことは何もできなくなる。

同様の技術はフィッシング問題を解決するためにも使用できる。ユーザーが2つのパスワードでアカウントを保護していれば、大部分において解決可能である。

この技術は、クロスサイトリクエストフォージェリ（CSRF）攻撃に対しても有用である。

#### HTTP Cookieへのセッション識別子の保存
最近のシステムのほとんどにおいて、セッション識別子はデフォルトでHTTP cookieに保存される。セッションシステムがGET/POSTの値を無視する限り、これは中程度のセキュリティレベルを持つ。しかし、この解決策はクロスサイトリクエストフォージェリに対して脆弱であり、RESTのステートレス性要件を満たさない。

#### SSL / TLSセッション識別子の利用
HTTPSセキュリティを有効にしている場合、一部のシステムではアプリケーションがSSL / TLSセッション識別子を取得できる。SSL/TLSセッション識別子の使用は非常に安全だが、多くのWeb開発言語はこれに対する堅牢な組み込み機能を提供していない。

### リクエストごとのSID再生成
セッション固定に対する対策の一つは、リクエストごとに新しいセッション識別子（SID）を生成することである。これを行えば、攻撃者がユーザーを騙して既知のSIDを受け入れさせたとしても、攻撃者がそのSIDを再利用しようとしたときには無効になっている。このようなシステムの実装は単純であり、以下の手順で示される。

* HTTPリクエストから以前のセッション識別子 `OLD_SID` を取得する。
* `OLD_SID` がnullまたは空であるか、SID=`OLD_SID` を持つセッションが存在しない場合、新しいセッションを作成する。
* 安全な乱数生成器を使用して新しいセッション識別子 `NEW_SID` を生成する。
* セッションを SID=`NEW_SID` で識別させる（SID=`OLD_SID` ではなくなる）。
* 新しいSIDをクライアントに送信する。

例：

マロリーがアリスを騙して `` を訪問させることに成功した場合、以下のHTTPリクエストが `victim.example.com` に送信される。
```http
GET /?SID=I_KNOW_THE_SID HTTP/1.1
Host: victim.example.com
```
`victim.example.com` は `SID=I_KNOW_THE_SID` を受け入れるが、通常これは悪いことである。しかし、`victim.example.com` はセッション再生成を実行するため安全である。`victim.example.com` は以下の応答を返す。
```http
HTTP/1.1 200 OK
Set-Cookie: SID=3134998145AB331F
```
上述の例では、アリスはマロリーにとって未知の `SID=3134998145AB331F` を使用することになり、`SID=I_KNOW_THE_SID` は無効となる。したがって、マロリーのセッション固定の試みは失敗する。

残念ながら、セッション再生成が常に可能とは限らない。ActiveXやJavaアプレットなどのサードパーティ製ソフトウェアが使用されている場合や、ブラウザプラグインがサーバーと通信する場合に問題が発生することが知られている。サードパーティ製ソフトウェアがログアウトを引き起こしたり、セッションが2つの別々のセッションに分割されたりする可能性がある。

セッションの実装がGETやPOST変数を通じたSIDの送信を含んでいる場合、この対策はほとんどのブラウザで「戻る」ボタンを使用不能にする可能性がある。ユーザーが以前のリクエストからの古い無効なセッション識別子を使用することになるためである。

### サーバー生成SIDのみを受け入れる
セキュリティを向上させる一つの方法は、サーバーによって生成されていないセッション識別子を受け入れないことである。しかし、前述のように、これはすべてのセッション固定攻撃を防ぐわけではない。
```php
if (!isset($_SESSION['SERVER_GENERATED_SID'])) {
    session_destroy(); // セッション内の全データを破棄
}
session_regenerate_id(); // 新しいセッション識別子を生成
$_SESSION['SERVER_GENERATED_SID'] = true;
```


### ログアウト機能
ログアウト機能は、ユーザーがセッションによるそれ以降のリクエストを許可しないことを示せるため有用である。これにより、攻撃はセッションがアクティブな間しか効果を発揮しない。なお、以下のコードはクロスサイトリクエストフォージェリチェックを行っていないため、攻撃者がユーザーを強制的にWebアプリケーションからログアウトさせる可能性があることに注意されたい。
```php
if (logout) {
    session_destroy(); // セッション内の全データを破棄
}
```


### 古いSIDのタイムアウト
この防御策は実装が簡単であり、放置された可能性のある端末を使用して権限のないユーザーが正規ユーザーのアカウントにアクセスすることに対して、ある程度の保護を提供する利点がある。

そのSIDによる最後のアクセス時刻を含むセッション変数を保存する。そのSIDが再び使用されたとき、現在のタイムスタンプをセッションに保存されているものと比較する。差分が事前に定義された数値（例えば5分）より大きい場合、セッションを破棄する。そうでなければ、セッション変数を現在のタイムスタンプで更新する。

### リファラが疑わしい場合にセッションを破棄する
ページを訪問すると、ほとんどのWebブラウザはリファラ（Referrer）ヘッダーを設定する。これは、このページに到達するために辿ったリンクが含まれていたページのことである。

ユーザーがそのサイト外からリンクされる可能性が低いサイト（例：銀行のWebサイトやWebメール）にログインしており、かつユーザーが長時間ログインしたままにしないようなサイトである場合、リファラはそのサイトからのものであるはずである。それ以外のリファラは疑わしいと見なすべきである。ただし、元のリクエストがHTTPSページからのものである場合、リファラは削除されるため、このセキュリティシステムに依存することはできない。

例えば、`` は以下のセキュリティチェックを採用できる。
```php
if (strpos($_SERVER['HTTP_REFERER'], 'http://vulnerable.example.com/') !== 0) {
    session_destroy(); // セッション内の全データを破棄
}
session_regenerate_id(); // 新しいセッション識別子を生成
```


### セッションを通じて追加情報が一貫していることを検証する
セキュリティをさらに向上させる一つの方法は、ユーザーが同一のエンドユーザー（クライアント）であるように見えることを保証することである。これにより、セッション固定やその他の攻撃を実行するのが少し難しくなる。

RFC 3704やその他のアンチスプーフィング手法に準拠するネットワークが増えるにつれて、IPアドレスは「同一ソース」の識別子としてより信頼できるものになっている。したがって、Webサイトのセキュリティは、セッションを通じて送信元IPアドレスが一貫していることを検証することで向上させることができる。

これは以下の方法で実行できる。
```php
if ($_SERVER['REMOTE_ADDR'] != $_SESSION['PREV_REMOTEADDR']) {
    session_destroy(); // セッション内の全データを破棄
}
session_regenerate_id(); // 新しいセッション識別子を生成
$_SESSION['PREV_REMOTEADDR'] = $_SERVER['REMOTE_ADDR'];
```
ただし、このアプローチを採用する前に考慮すべき点がいくつかある。

* 複数のユーザーが1つのIPアドレスを共有している場合がある。NATを使用して建物全体で1つのIPアドレスを共有することは珍しくない。
* 1人のユーザーが不一貫なIPアドレスを持つ場合がある。これはプロキシの背後にいるユーザー（AOLの顧客など）に当てはまる。また、一部のモバイル/ローミングユーザーや、負荷分散されたインターネット接続の背後にいるユーザーにも当てはまる。IPv6プライバシー拡張が有効になっているユーザーも、いつでもIPv6プライバシーアドレスを変更する可能性がある。
* デュアルスタッククライアントでは、リクエストがIPv4とIPv6の間で移動するため、確実に機能しない。
* モバイルユーザーもアドレス間をローミングするため、確実には機能しない。

サイトによっては、追加のセキュリティが利便性の欠如を上回る場合もあるし、そうでない場合もある。

#### ユーザーエージェントの識別
ブラウザは「User-Agent」HTTPヘッダーによって自身を識別する。このヘッダーは通常の使用中に変更されることはないため、もし変更されれば非常に疑わしい。Webアプリケーションは、悪意のあるユーザーがセッションを盗むのを防ぐために、User-Agent検出を利用する場合がある。しかし、これは回避するのが些細なことであり、攻撃者は自分のサイトで被害者のユーザーエージェントを簡単にキャプチャし、攻撃中にそれを偽装することができる。この提案されたセキュリティシステムは、隠蔽によるセキュリティに依存している。
```php
if ($_SERVER['HTTP_USER_AGENT'] != $_SESSION['PREV_USERAGENT']) {
    session_destroy(); // セッション内の全データを破棄
}
session_regenerate_id(); // 新しいセッション識別子を生成
$_SESSION['PREV_USERAGENT'] = $_SERVER['HTTP_USER_AGENT'];
```
ただし、このアプローチを採用する前に考慮すべき点がいくつかある。

* インターネットカフェなどでは、複数のユーザーが同じブラウザのUser Agentを持つ場合がある。
* 複数のユーザーが同じデフォルトブラウザを持つ場合がある（例：Windows XP SP3のInternet Explorer 6や、携帯電話のミニブラウザ）。

しかし、User Agentが正当に変更されるケースもいくつかある。

以下の例は同一ユーザーである。

* 画面が回転したスマートフォン（最後のリクエスト以降）：
** 
** 
* Internet Explorerの互換モード：
** 
** 
* 複数のサーバーに分散されたプロキシを介してWebサイトにアクセスしているユーザーで、すべてのサーバーが最新バージョンのプロキシソフトウェアにアップグレードされていない場合：
** 
** 

## 多層防御
多層防御とは、複数の対策を組み合わせることである。考え方は単純で、1つの障害を克服するのが些細なことであっても、複数の障害を克服するのは非常に困難になる可能性があるというものである。

多層防御戦略には以下が含まれる可能性がある。

* HTTPSを有効にする（他の問題から保護するため）
* 正しい設定（外部SIDを受け入れない、タイムアウトの設定など）
* session_regenerateの実行、ログアウトのサポートなど

HTTPリファラはSSL/TLS (HTTPS) では渡されない。

以下のPHPスクリプトは、多層防御の方法で組み合わされたいくつかの対策を示している。
```php
if (isset($_GET['LOGOUT']) ||
    $_SERVER['REMOTE_ADDR'] !== $_SESSION['PREV_REMOTEADDR'] ||
    $_SERVER['HTTP_USER_AGENT'] !== $_SESSION['PREV_USERAGENT']) {
    session_destroy();
}

session_regenerate_id(); // 新しいセッション識別子を生成

$_SESSION['PREV_USERAGENT'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['PREV_REMOTEADDR'] = $_SERVER['REMOTE_ADDR'];
```
このコードは、現在の REMOTE_ADDR（ユーザーのIPアドレス）とUser-agentを、以前のリクエストの REMOTE_ADDR および User-agent と照合していることに注意されたい。これについては前述のように、一部のサイトでは不便になる可能性がある。

## 関連項目
* セッションハイジャック
* 権限昇格
* クロスサイトリクエストフォージェリ（CSRF）

## 脚注