メインコンテンツまでスキップ

セッションセキュリティ

このドキュメントの目的

Webアプリケーションにおけるセッション管理のセキュリティリスクと、安全な実装方法を学びます。


セッション管理の基本

セッションとは

HTTP はステートレスプロトコル

ユーザーの状態(ログイン状態等)を維持する仕組みが必要

セッション管理

セッションID: ユーザーを識別するための一意な識別子
セッションデータ: ユーザーに紐づく情報(サーバー側で保持)

セッションが払い出されるまでの流れ

┌──────────┐                           ┌──────────┐
│ ブラウザ │ │ サーバー │
└────┬─────┘ └────┬─────┘
│ │
│ 1. サイトにアクセス │
│ ────────────────────────────────────>│
│ │
│ 2. セッションID生成
│ (abc123) │
│ │
│ 3. Set-Cookie: session_id=abc123 │
│ <────────────────────────────────────│
│ │
│ ┌────────────────────────────────┐ │
│ │ Cookieに保存 │ │
│ │ session_id=abc123 │ │
│ └────────────────────────────────┘ │
│ │
│ 4. ログインフォーム送信 │
│ Cookie: session_id=abc123 │
│ ────────────────────────────────────>│
│ │
│ 5. 認証成功 │
│ │
│ 【重要】 │
│ 6. セッションID再生成
│ abc123 → xyz789
│ ※固定攻撃対策
│ │
│ 7. 新しいセッションにユーザー情報を紐づけ
│ sessions[xyz789] = {user: "alice"}
│ (abc123は破棄)
│ │
│ 8. Set-Cookie: session_id=xyz789 │
│ <────────────────────────────────────│
│ │
│ 9. 以降のリクエスト │
│ Cookie: session_id=xyz789 │
│ ────────────────────────────────────>│
│ │
│ 10. セッションIDで
│ ユーザーを識別
│ → "alice"として処理
│ │

ポイント:

  • 認証成功後にセッションIDを再生成する(ステップ7)
  • これがセッション固定攻撃への対策

セッション管理の方式

方式保存場所セキュリティスケーラビリティ
サーバーサイドセッションサーバーメモリ/DB
Cookie(暗号化)クライアント
JWT(ステートレス)クライアント

セッション攻撃の種類

攻撃と対策の早見表

攻撃何が起こるか主な対策
セッションハイジャックセッションIDを盗まれてなりすまされるHTTPS、HttpOnly Cookie、CSP
セッション固定攻撃攻撃者が用意したセッションIDでログインさせられる認証後にセッションID再生成
セッション予測攻撃セッションIDを推測されてなりすまされる暗号学的に安全な乱数を使用

1. セッションハイジャック

攻撃手法:

1. 攻撃者がセッションIDを盗聴/推測
2. 盗んだセッションIDを使用
3. 正規ユーザーになりすまし

盗聴経路:

  • HTTPでの通信(暗号化なし)
  • XSS攻撃でJavaScriptからCookieを取得
  • 中間者攻撃(MITM)
  • ログファイルに記録されたセッションID

対策:

盗聴経路ごとに対策が異なる

1. HTTP通信での盗聴
→ HTTPS必須化 + HSTS設定
→ CookieにSecure属性を設定

2. XSS攻撃でのCookie取得
→ CookieにHttpOnly属性を設定
→ CSP(Content Security Policy)を設定

3. 中間者攻撃(MITM)
→ HTTPS必須化
→ 証明書の検証

4. ログファイルへの記録
→ セッションIDをログに出力しない
→ ログのアクセス制御

2. セッション固定攻撃(Session Fixation)

攻撃手法:

┌─────────┐      ┌─────────┐      ┌─────────┐
│ 攻撃者 │ │ サーバー │ │ ユーザー │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
│ 1. サイトにアクセス │
│───────────────>│ │
│ │ │
│ 2. SESSION_ID=abc123 │
│<───────────────│ │
│ │ │
│ 3. URLを送付 │ │
│ example.com/login?sid=abc123 │
│────────────────────────────────>│
│ │ │
│ │ 4. URLでアクセス│
│ │<───────────────│
│ │ │
│ │ 5. ログイン │
│ │<───────────────│
│ │ │
│ │ SESSION_ID=abc123 のまま
│ │ ログイン済み状態に
│ │ │
│ 6. abc123でアクセス │
│───────────────>│ │
│ │ │
│ 7. ユーザーとして│ │
│ ログイン済み!│ │
│<───────────────│ │

対策:

認証成功後にセッションIDを再生成する

1. 古いセッションを無効化
2. 新しいセッションIDを発行
3. ユーザー情報を新しいセッションに移行

3. セッション予測攻撃

攻撃手法:

セッションIDの生成アルゴリズムが予測可能な場合:

例: 連番
SESSION_ID=1000
SESSION_ID=1001
SESSION_ID=1002
→ 次は1003と予測可能

例: タイムスタンプベース
SESSION_ID=1703500000001
→ 時刻から推測可能

対策:

暗号学的に安全な乱数生成器を使用
- 256ビット以上のエントロピー
- SecureRandom等の暗号論的乱数生成器

重要な属性

属性目的設定値
SecureHTTPS接続のみで送信必須
HttpOnlyJavaScriptからのアクセス禁止必須
SameSiteCSRF対策Strict または Lax
PathCookieの有効範囲/
DomainCookieの有効ドメイン明示的に設定
Max-Age/Expires有効期限適切な値

設定例

Set-Cookie: session_id=abc123;
Secure;
HttpOnly;
SameSite=Strict;
Path=/;
Max-Age=3600

各属性の詳細

Secure属性

Secure なし:
HTTP接続でもCookieが送信される
→ 盗聴リスク

Secure あり:
HTTPS接続時のみCookieが送信される
→ 盗聴防止

HttpOnly属性

// HttpOnly なし
document.cookie // セッションIDが取得可能
// XSS攻撃で盗まれるリスク

// HttpOnly あり
document.cookie // セッションIDにアクセス不可
// XSS攻撃でも盗めない

SameSite属性

SameSite=Strict:
- クロスサイトリクエストでは一切Cookieを送信しない
- 最も安全だが、外部サイトからのリンクでログイン状態が維持されない

SameSite=Lax:
- トップレベルナビゲーション(リンククリック)では送信
- POSTリクエストやiframe/imgでは送信しない
- 推奨設定

SameSite=None:
- クロスサイトでも送信(Secureが必須)
- サードパーティCookieが必要な場合のみ

セッションライフサイクル管理

セッションタイムアウト

種類:

1. 絶対タイムアウト(Absolute Timeout)
- セッション作成から一定時間で強制終了
- 例: 8時間

2. アイドルタイムアウト(Idle Timeout)
- 最後のアクティビティから一定時間で終了
- 例: 30分

3. 更新タイムアウト(Renewal Timeout)
- セッションIDを定期的に再生成
- 例: 15分ごと

推奨値:

シナリオ絶対タイムアウトアイドルタイムアウト
一般的なWebアプリ8時間30分
金融系サービス1時間5分
管理画面4時間15分
「ログイン状態を維持」30日7日

セッション終了(ログアウト)

適切なログアウト処理

1. サーバー側セッションを無効化
2. セッションCookieを削除(Max-Age=0)
3. 関連するトークンを失効(OAuth/OIDC)
4. 監査ログ記録

全デバイスからのログアウト

1. ユーザーの全セッションを無効化
2. 全リフレッシュトークンを失効
3. 監査ログ記録

同時セッション制御

ポリシーオプション

ポリシー動作ユースケース
無制限何台でも同時ログイン可能一般的なサービス
単一セッション新しいログインで古いセッションを無効化セキュリティ重視
最大N個N個を超えると最古のセッションを無効化バランス型
拒否N個を超えると新しいログインを拒否厳格なセキュリティ

分散環境でのセッション管理

課題

ロードバランサー配下の複数サーバー:

ユーザー → LB → Server A(セッション作成)
ユーザー → LB → Server B(セッションなし!)

解決策

1. スティッキーセッション

- ロードバランサーが同じユーザーを同じサーバーに振り分け
- 欠点: サーバー障害時にセッション喪失

2. セッションレプリケーション

- サーバー間でセッションデータを同期
- 欠点: ネットワーク帯域、遅延

3. 外部セッションストア

推奨: Redis/Memcached等に集中管理

Server A ─┐
Server B ─┼─→ Redis(セッションストア)
Server C ─┘

SPAアーキテクチャでのセッション(BFFパターン)

SPAでOAuth/OIDCを使う場合、BFF(Backend For Frontend)パターンでセッションを活用することがある。

なぜSPAでセッションを使うのか

SPAでのトークン保存の課題:
- localStorage/sessionStorage → XSSで盗まれる
- メモリ → ページリロードで消える



BFFを挟んでセッションで管理する

┌───────┐ セッションCookie ┌───────┐ Access Token ┌───────┐
│ SPA │ ←────────────────→ │ BFF │ ←────────────→ │ API │
└───────┘ (HttpOnly) └───────┘ └───────┘

- SPAはトークンを持たない(セッションCookieのみ)
- BFFがトークンを安全に保持
- XSSでトークンが盗まれるリスクを軽減

BFFでのセッション管理の考慮点

1. ステートフルになる
┌───────────────────────────────────────────────┐
│ BFFがセッション状態を持つ │
│ → スケールアウト時に外部ストア(Redis等)が必要 │
└───────────────────────────────────────────────┘

2. セッションとトークンのライフサイクル
┌───────────────────────────────────────────────┐
│ セッション有効期限 vs Access Token有効期限 │
│ → 両方を適切に管理する必要がある │
│ → セッション切れ時にトークンも失効させる │
└───────────────────────────────────────────────┘

3. セキュリティ対策は通常のセッションと同じ
- セッション固定攻撃対策(認証後に再生成)
- Cookie属性(HttpOnly, Secure, SameSite)
- タイムアウト設定

トレードオフ

┌─────────────────┬─────────────────────────────────────┐
│ メリット │ デメリット │
├─────────────────┼─────────────────────────────────────┤
│ XSSでトークン │ 実装が複雑になる │
│ が盗まれない │ │
├─────────────────┼─────────────────────────────────────┤
│ サーバー側で │ ステートフルになり │
│ 制御しやすい │ スケーラビリティに影響 │
├─────────────────┼─────────────────────────────────────┤
│ 枯れた技術で │ BFF自体が攻撃対象になる │
│ 知見が多い │ │
└─────────────────┴─────────────────────────────────────┘

→ セキュリティ要件が高い場合(金融系等)に検討
→ 詳細は [トークン保存のセキュリティ](./05-token-storage-security.md) を参照

ブラウザによるCookie制限の違い

概要

各ブラウザはプライバシー保護のため、サードパーティCookieに対して異なる制限を設けています。OAuth/OIDCフローでサブドメイン間のセッション管理を行う場合、これらの制限を理解することが重要です。

ブラウザ別の制限

ブラウザサードパーティCookie主な制限技術
Safari厳格にブロックITP (Intelligent Tracking Prevention)
Firefoxブロック傾向ETP (Enhanced Tracking Protection)
Chrome段階的に廃止予定Privacy Sandbox
EdgeChromeに準拠-

Safari ITP (Intelligent Tracking Prevention)

SafariのITPは最も厳格なCookie制限を持ちます。

ITPの主な制限:

1. サードパーティCookieのブロック
- 異なるドメインからのCookieは基本的にブロック
- サブドメイン間でも制限される場合がある

2. ファーストパーティCookieの有効期限制限
- JavaScriptで設定したCookieは7日で失効
- Cross-site trackingと判定されると24時間で失効

3. リダイレクトチェーンでのCookie制限
- A → B → A のようなリダイレクトでBが設定したCookieをブロック

OAuth/OIDCフローでの問題

サブドメイン構成でのOAuthフローでは、SafariのITPが問題を引き起こすことがあります。

問題のあるフロー(サーバー間通信でCookie転送):

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ sample.local.test│ │ api.local.test │ │ auth.local.test │
│ (クライアント) │ │ (認可サーバー) │ │ (認証画面) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ 1. POST /api/passkey-registration │
│─────────────────────────────────────────────>│
│ │ │
│ 2. サーバー間通信 │ │
│ POST /v1/authorizations │
│ ─────────────────>│ │
│ │ │
│ <─────────────────│ │
│ Set-Cookie: AUTH_SESSION=xxx │
│ │ │
│ 3. Cookie転送 + リダイレクト │
│ Set-Cookie: AUTH_SESSION=xxx (転送) │
│ Location: auth.local.test │
│<─────────────────────────────────────────────│
│ │ │
│ ↑ │
│ Safariがブロック! │
│ (sample.local.testがapi.local.testの │
│ Cookieを設定しようとしている) │

問題の原因:

  • sample.local.testのレスポンスでapi.local.test用のCookieを設定しようとしている
  • SafariはこれをサードパーティCookieとして拒否

解決策:ブラウザ直接リダイレクト

ブラウザが認可サーバーに直接アクセスすることで、Cookieがファーストパーティとして扱われます。

正しいフロー(ブラウザ直接リダイレクト):

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ sample.local.test│ │ api.local.test │ │ auth.local.test │
│ (クライアント) │ │ (認可サーバー) │ │ (認証画面) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
│ 1. GET /api/passkey-registration │
│────────>│ │ │
│ │ │ │
│ 2. 302 Redirect │ │
│ Location: api.local.test/v1/authorizations │
│<────────│ │ │
│ │ │
│ 3. ブラウザが直接アクセス │
│ GET api.local.test/v1/authorizations │
│ ─────────────────────>│ │
│ │ │
│ 4. 302 Redirect │ │
│ Set-Cookie: AUTH_SESSION=xxx │
│ Location: auth.local.test │
│<──────────────────────│ │
│ │ │
│ ↑ │
│ Cookieは受け入れられる! │
│ (api.local.testが自身のCookieを設定) │
│ │ │
│ 5. ブラウザがauth.local.testにアクセス │
│ Cookie: AUTH_SESSION=xxx │
│ ─────────────────────────────────────────────>│

ポイント:

  • ブラウザがapi.local.testに直接アクセス
  • api.local.testが自身のドメインでCookieを設定
  • Safariはファーストパーティcookieとして受け入れる

実装例

❌ 問題のある実装(サーバー間通信):

// sample.local.test のAPIルート
export async function GET() {
// サーバー間通信でCookieを取得
const authResponse = await fetch(`${idpServer}/v1/authorizations`, {
method: "POST",
redirect: "manual",
});

const response = NextResponse.redirect(authViewUrl);

// Cookie転送 → Safariでブロックされる
const setCookie = authResponse.headers.get("set-cookie");
response.headers.append("Set-Cookie", setCookie);

return response;
}

✅ 正しい実装(ブラウザ直接リダイレクト):

// sample.local.test のAPIルート
export async function GET() {
// 認可URLを構築
const authUrl = new URL(`${idpServer}/v1/authorizations`);
authUrl.searchParams.set("client_id", clientId);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("redirect_uri", callbackUrl);
authUrl.searchParams.set("state", state);

// ブラウザを直接認可サーバーにリダイレクト
return NextResponse.redirect(authUrl.toString());
}

Cookie設定のベストプラクティス

サブドメイン間でCookieを共有する場合:

推奨設定:
┌────────────────────────────────────────────────────┐
│ Set-Cookie: SESSION=xxx; │
│ Domain=.local.test; ← サブドメイン共有 │
│ SameSite=Lax; ← トップレベルナビ可 │
│ Secure; ← HTTPS必須 │
│ HttpOnly; ← XSS対策 │
│ Path=/ │
└────────────────────────────────────────────────────┘

SameSite属性の選択:

Safari対応ユースケース
Strict最も安全だが外部リンクでログアウト状態
Lax推奨:トップレベルナビゲーションでCookie送信
NoneSecureが必須、ITPの制限を受ける可能性

開発時の確認方法

Safariでの動作確認:

1. Safari → 設定 → プライバシー
- 「サイト越えトラッキングを防ぐ」が有効になっていることを確認

2. 開発 → Webインスペクタ → ストレージ → Cookie
- Cookieが正しく設定されているか確認

3. 開発 → Webインスペクタ → コンソール
- ITP関連の警告メッセージを確認

まとめ

┌─────────────────────────────────────────────────────────────┐
│ Safari/ITP対応のポイント │
├─────────────────────────────────────────────────────────────┤
│ 1. サーバー間通信でのCookie転送は避ける │
│ 2. ブラウザを認可サーバーに直接リダイレクトする │
│ 3. SameSite=Lax を使用する(Noneは避ける) │
│ 4. 開発時は複数ブラウザでテストする │
└─────────────────────────────────────────────────────────────┘

セキュリティチェックリスト

セッションID

  • 128ビット以上の暗号学的に安全な乱数
  • 認証成功後に再生成
  • URLパラメータではなくCookieで管理

Cookie設定

  • Secure属性を設定
  • HttpOnly属性を設定
  • SameSite属性を設定(Lax以上)
  • 適切なPath/Domainを設定

ライフサイクル

  • アイドルタイムアウトを設定
  • 絶対タイムアウトを設定
  • 適切なログアウト処理を実装

監視

  • 同時セッション数を制限
  • 異常なセッション活動を監視
  • セッション関連イベントをログ記録

参考資料


最終更新: 2026-01-20 対象: Webアプリケーション開発者