セッション管理
OWASP の Session Management Cheat Sheet を取得して読み、 理解した内容を自分の言葉で書いたもの。
認証と認可から切り出した。理由は、守る対象の寿命が違うから。
認証は一瞬で終わるが、セッションはそのあと数時間から数日にわたって「認証済みである」 という事実を持ち回る。だから MFA まで固めても、セッションIDが漏れればそこは素通りされる。 セッションIDはパスワードと同じ重みの資格情報として扱う、というのが全体を貫く前提。
HTTP はステートレスなので、セッションは仕様の外にある。 アプリケーション側が自前で作る仕組みなので、既定で安全ということがない。
セッションIDそのもの
| 性質 | 要件 |
|---|---|
| エントロピー | 64ビット以上。総当たりを成立させないため |
| 長さ | 16進エンコードなら 16文字以上(= 64ビット) |
| 生成 | CSPRNG を使う。通常の疑似乱数は不可 |
| 中身 | 意味を持たせない。サーバー側の索引にすぎない状態にする |
| 名前 | PHPSESSID JSESSIONID ASP.NET_SessionId のような既定名を避け、id のような汎用名にする |
IDの一部でも固定値や推測可能な値が混ざると、その分だけ実効エントロピーが落ちる。 「乱数64ビットに固定のプレフィックスを付ける」は問題ないが、 「ユーザーIDとタイムスタンプを連結して一部を乱数にする」は落ちる。
中身を空にする理由は情報漏洩の防止。ユーザー名・権限・メールアドレスをIDに埋めない。 意味はすべてサーバー側のセッションオブジェクトに置き、IDはそれを引く鍵だけにする。
既定名を避けるのは、技術スタックを名乗らないため。 攻撃準備の事前調査でそのまま拾われる。
受け渡しは Cookie に限る
URL にセッションIDを載せない。
ブラウザの履歴に残る
アクセスログに残る
ブックマークで共有される
Referer ヘッダで外部サイトへ漏れる
検索エンジンに拾われる
さらに、URL に載る形式はセッション固定化(後述)を容易にする。 リンクを1本送るだけで、攻撃者が用意したIDを相手に使わせられるため。
Cookie 以外の経路で送られてきたセッションIDは受け付けない。 受け付けないこと自体が固定化対策になる。
Cookie 属性
| 属性 | 指定 | 効果 |
|---|---|---|
Secure | 必須 | HTTPS のときだけ送出する。平文で流れない |
HttpOnly | 必須 | document.cookie から読めない。XSS での窃取を防ぐ |
SameSite | Strict を基本 | クロスサイトのリクエストに乗らない。CSRF の前提を崩す |
Domain | 指定しない | 指定しなければ発行元のホストだけに限定される |
Path | できるだけ狭く | 適用範囲を絞る |
Expires / Max-Age | 指定しない | 非永続(ブラウザを閉じたら消える)にする |
SameSite=None を Secure なしで使わない。ブラウザの既定値にも依存しない。
__Host- プレフィックス
Set-Cookie: __Host-id=<値>; Secure; HttpOnly; SameSite=Strict; Path=/
__Host- を名前の先頭に付けると、Secure があること・Domain がないこと・
Path=/ であることをブラウザ側が検査し、満たさない Cookie を拒否する。
ここが効くところで、設定ミスをサーバー側のレビューではなくブラウザに検査させられる。
サブドメイン間で共有する必要がある場合だけ __Secure- を使う。
認証のたびに作り直す
権限のレベルが変わった瞬間に、必ず新しいセッションIDを発行する。
未認証 → 認証済み(ログイン)
パスワード変更
権限・ロールの昇格
重要な資源へのアクセス
これがセッション固定化(session fixation)への対策で、事実上これ以外にない。
固定化は、攻撃者が自分の知っているIDを先に相手のブラウザへ食わせておき、 そのまま相手にログインさせる攻撃。ログイン後もIDが変わらなければ、 攻撃者は最初から知っているIDで認証済みのセッションに入れる。 IDを盗む必要すらないのが厄介なところ。
受け取ったIDの扱いには2つのモードがある。
| モード | 挙動 |
|---|---|
| 厳格(推奨) | 自分が発行したIDだけを受け付ける。未知のIDが来たら新規発行し、疑わしい事象として記録する |
| 寛容 | 送られてきたIDをそのまま採用する。固定化にそのまま通じる |
セッションIDは利用者の入力として扱う。 Cookie 由来であっても、 検証せずにログや SQL に流すと、そこがインジェクションの入口になる。
有効期限
3種類あり、役割が違う。
| 種類 | 何を締める | 目安 |
|---|---|---|
| アイドル(無操作) | 放置されたセッション | 重要度が高いもの 2〜5分 / 低リスク 15〜30分 |
| 絶対 | 操作の有無によらない上限 | 業務アプリで 4〜8時間 |
| 更新 | 長寿命セッションのIDを定期的に振り直す | 例: 30分ごと |
アイドルタイムアウトは露出時間を縮めるだけで、 乗っ取ったセッションを能動的に使い続けている攻撃者には効かない。 だから絶対タイムアウトが要る。
期限の判定はすべてサーバー側で行う。 「ログインからの経過分数」をクライアントが持つ形にすると、そこが改竄される。 クライアント側のカウントダウン表示は、あくまで利用者への案内。
終了させる
ログアウトはサーバー側でセッションを破棄する。 Cookie を消すだけでは、 サーバーにセッションが残っていて、IDを知っている側からは使えたままになる。
J2EE HttpSession.invalidate()
ASP.NET Session.Abandon()
PHP session_destroy()
クライアント側の後始末も併せて行う。
Set-Cookie: id=; Expires=<過去の日時>
Cache-Control: no-store
Clear-Site-Data: "cache", "cookies", "storage"
Cache-Control: no-store は、セッションIDや認証後の画面がキャッシュに残らないようにするもの。
共用端末では、これがないと戻るボタンで内容が読める。
ログアウトのボタンは、すべての画面から届く位置に置く。 見つからないログアウトは、存在しないのと変わらない。
localStorage に置かない
localStorage / sessionStorage にセッションID・認証トークン・JWT・リフレッシュトークンを置かない。
これらはオリジン内で動くすべての JavaScript から読める。
XSS が1つあれば、そこに入っている資格情報が全部出ていく。
HttpOnly Cookie にはこの経路がない。
代替として Web Worker がある。Worker のメモリに置けば、
メインウィンドウの JavaScript から値そのものは読めない。
XSS があっても「Worker に処理を依頼する」ことはできてしまうが、
秘密自体が露出しない点で HttpOnly に近い性質になる。
検知と記録
記録する事象。
セッションの生成・更新・破棄
ログイン、ログアウト、権限の変更
タイムアウトによる失効
不正なセッションID の提示
重要な操作
ログに生のセッションIDを書かない。ソルト付きハッシュを書く。 理由は、ログ自体が有効な資格情報の貯蔵庫になってしまうから。 ハッシュにしておけば同一セッションの追跡はでき、ログが漏れても悪用はできない。
異常の検知として、セッションIDを IP アドレスや User-Agent に結びつける方法がある。 ただしこれは検知であって防御ではない。 NAT や企業プロキシの下では正規の利用者でも IP が動くので、誤検知が出る。 遮断ではなく、まず記録と警告に使う。
短時間に多数の異なるセッションIDが試されるのは、推測・総当たりの兆候。 試行を数え、警告するか送信元を遮断できるようにしておく。
同時ログインを許すかは設計判断。許す場合でも、 利用者が自分の有効なセッションの一覧を見て、遠隔で切れるようにしておくと、 乗っ取りに気づいた利用者が自力で対処できる。
攻撃側との対応
| 攻撃 | 効く対策 |
|---|---|
| 固定化(fixation) | 認証後にIDを作り直す。未知のIDを受け付けない |
| 乗っ取り(hijacking) | 十分なエントロピー、TLS、Secure / HttpOnly / SameSite、異常検知 |
| 推測・総当たり | 64ビット以上のエントロピー、試行回数の監視と遮断 |
| XSS 経由の窃取 | HttpOnly、Web Storage を使わない |
| CSRF | SameSite=Strict、XSRF トークン |
既存の記述との対応
| 既にある記述 | この文書との関係 |
|---|---|
| 認証と認可 の再認証 | 権限が変わる操作の前後。ここではIDの再発行として書いた |
| 暗号と鍵の管理 の CSPRNG | セッションIDの生成に使う乱数はこちら |
API 設計 の Secure / HttpOnly | 属性の一覧。本文書では SameSite と __Host- を足した |
| 入力検証と出力エスケープ | XSS が通ると HttpOnly 以外の防御は崩れる |
| 可観測性 | セッション事象のログはここへ流す |
関連
- OWASP Top 10 — A07 Authentication Failures の後半
- 認証と認可 — 「誰か」を決める側
- 暗号と鍵の管理 — IDの生成に使う乱数
- 入力検証と出力エスケープ — 窃取の主経路
- 侵入 — 攻撃側から見た同じ場面
- 設定と環境 — セッションをプロセスのメモリに置かない理由