TL

セッション管理の原理(セッション固定・ハイジャック対策)

ログイン状態を支えるセッションIDが盗まれれば、パスワードなしで成りすまされる。ID生成のエントロピー要件と Cookie 属性の正しい設定を原理から押さえ、固定化・ハイジャックを確実に塞げる。

応用セキュリティセッションCookie認証セッション固定最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.セッションはサーバ側保持(IDだけ渡す)かステートレス(署名付きトークンに状態を入れる)の二択。前者は失効が即時、後者はスケールしやすいが取り消しが難しいトレードオフがある。
  • 2.セッションIDは推測不能が絶対条件。CSPRNG 由来で最低128ビットのエントロピーを持たせ、ログイン直後にIDを再発行(regenerate)してセッション固定攻撃を無効化する。
  • 3.ハイジャック・サイドジャッキングは Cookie を盗ませない設計で防ぐ。HttpOnly で JS から隔離、Secure で平文送信を禁止、SameSite で別サイト送信を絞るのが基本三点セット。

なぜセッションが必要なのか

HTTP は本来ステートレスで、各リクエストは互いに独立しています。サーバーは「いま届いたこのリクエストが、さっきログインした人と同じ人か」を、リクエスト単体からは判断できません。そこで一度だけ本人確認(認証)を済ませ、その結果を表す秘密の合言葉を発行し、以降のリクエストに付けてもらう——これがセッションの本質です。

つまりセッションIDは「ログイン状態そのもの」を代理する値です。だからこの値が漏れれば、攻撃者はパスワードを知らなくても本人として振る舞えます。セッション管理の設計とは、突き詰めれば「この合言葉をどう作り、どう運び、どう守るか」に尽きます。

サーバ側セッション vs ステートレス

セッション状態をどこに置くかで、二つの方式に大別されます。

観点サーバ側セッションステートレス(署名付きトークン)
保存場所サーバー(メモリ/Redis/DB)。Cookie にはIDのみクライアント。状態を本体に詰めて持たせる
検証方法IDでストアを引き、状態を取り出すサーバーの鍵で署名(HMAC等)を検証する
即時失効ストアから消せば即無効。容易有効期限まで原則有効。取り消しが難しい
スケール共有ストアが要る(水平展開の足かせ)サーバーが状態を持たず展開しやすい
改ざん耐性状態はサーバー内なので安全署名で保証。鍵漏洩・alg混乱に注意

ステートレス方式(JWT がその代表)は「状態をクライアントに持たせ、署名で改ざんを検出する」発想です。スケールしやすい反面、サーバーは発行済みトークンを覚えていないため、「ログアウト即無効化」や「漏洩したトークンの強制失効」が苦手です。これを補うには有効期限を短くし、失効リスト(ブロックリスト)を別途持つ——結局サーバー側に状態が戻ってくる、というジレンマがあります。

ステートレスは“取り消せない”を直視する

署名付きトークンは「鍵で検証が通る限り有効」です。盗まれたトークンは、有効期限が切れるまでサーバー側からは止められません。重要操作を扱うシステムでは、短い有効期限+リフレッシュ、あるいはサーバ側セッションへの回帰を検討します。「JWT を長命セッションIDとして Cookie に入れる」のは両者の悪いとこ取りになりがちです。

セッションID生成のエントロピー要件

セッションIDの安全性は、ただ一点「攻撃者が当てられないこと」に懸かっています。逐次的なID(user1001, user1002...)や時刻ベースの値は、推測・列挙されて即座に破られます。要件は明確です。

  • 予測不可能性:暗号論的に安全な乱数(CSPRNG)で生成する。Math.random()rand() などの一般用途の擬似乱数は内部状態を逆算され得るため厳禁。
  • 十分なエントロピー:OWASP は最低 64 ビット、実務では 128 ビット以上を推奨。これは総当たりや、有効IDが空間にどれだけ密に存在するか(在席率)を踏まえた基準です。
  • 当て推量への耐性:攻撃者が秒間に試せる回数を踏まえ、有効なIDを引き当てる確率を無視できる水準に下げる。

ざっくり言うと、安全性の余裕は「IDのビット長」と「同時に有効なセッション数」の差で決まります。128 - log2(有効セッション数) がおおよその実効ビット数で、これが大きいほど推測は非現実的になります。たとえば100万セッション(約 2^20)が有効でも、128ビットIDなら実効 108 ビットが残り、総当たりは事実上不可能です。

# 128ビット = 16バイトを CSPRNG から取り、URLセーフに符号化する例
session_id = base64url( CSPRNG_bytes(16) )   # 例: "kJ3xQ9...22文字程度"
# NG: ユーザーID連番、時刻、UUIDv1(MAC/時刻由来で予測余地あり)
# OK: CSPRNG 由来の十分長いランダム値(UUIDv4 でも可だが長さ・出所に注意)
“当てる”だけでなく“盗む”経路も塞ぐ

強いIDは推測を防ぎますが、後述のハイジャックは「推測せずに本物を盗む」攻撃です。エントロピー要件は必要条件であって十分条件ではありません。生成(強いID)と運搬(Cookie 属性)の両輪が揃って初めてセッションが守られます。

三つの攻撃:固定・ハイジャック・サイドジャッキング

代表的な三つの攻撃は、狙う場所が異なります。混同せず、それぞれに対策を当てるのが要点です。

攻撃やること本質的な弱点効く対策
セッション固定攻撃者が用意したIDを被害者にログインさせるログイン後もIDが変わらないことログイン時にIDを再発行(regenerate)
セッションハイジャック有効なIDを盗み、本人に成りすますIDが漏れること(XSS・ログ・盗聴)HttpOnly・Secure・短命化・再認証
サイドジャッキング同一網の平文通信からCookieを傍受HTTP(非暗号)でCookieが流れること全面HTTPS+Secure属性+HSTS

セッション固定(Session Fixation) は少し直感に反します。攻撃者はまず自分で有効なセッションIDを取得し、それを被害者に押し付けます(URL パラメータや Cookie 注入で)。被害者がそのIDのままログインすると、攻撃者は「自分が知っているID」でログイン済み状態に相乗りできます。急所は「ログイン前後でIDが変わらない」点です。だから対策は単純で、認証成功の瞬間に必ずIDを作り直します。

// ログイン成功時:古いIDを捨てて新しいIDを発行する(固定化を無効化)
app.post('/login', async (req, res) => {
  const user = await verifyCredentials(req.body);   // 本人確認
  if (!user) return res.status(401).send('Unauthorized');
  await req.session.regenerate();                   // ← 旧IDを破棄し新IDを採番
  req.session.userId = user.id;                     // 認証情報は再発行後に格納
  res.redirect('/dashboard');
});

セッションハイジャック は有効なIDを盗む攻撃の総称です。代表経路は XSS による Cookie 読み取り、URL に載ったIDの Referer・ログ漏れ、共有端末での残存などです。盗まれたIDはそのまま本人の権限で使えてしまいます。

サイドジャッキング(Sidejacking) はハイジャックの一種で、同一ネットワーク(公衆 Wi-Fi など)で平文 HTTP のセッション Cookie を傍受します。Firesheep が有名にした古典的攻撃で、対策は「Cookie を平文で流さない」こと、すなわち全面 HTTPS と Secure 属性に尽きます。

Cookie 属性による防御

セッションIDの運搬は通常 Cookie です。Cookie の属性は、上の攻撃を運搬経路で塞ぐための制御スイッチです。詳細な属性挙動は Web 認証と Cookie でも扱っていますが、セッション保護の観点で要点を押さえます。

属性効果防げる攻撃
HttpOnlydocument.cookie からの読み取りを禁止XSS 経由のID窃取(ハイジャック)
SecureHTTPS 接続でのみ送信(平文では送らない)サイドジャッキング・盗聴
SameSite別サイト発リクエストへの自動付与を制限CSRF・一部のID持ち出し
__Host- 接頭辞Secure必須・Path=/・Domain指定不可を強制サブドメインからのCookie上書き/固定

HttpOnly は JavaScript からセッション Cookie を切り離します。XSS が起きても document.cookie ではIDを読めないため、窃取のハードルが上がります(ただし XSS 下でも「ブラウザに送らせる」操作は可能なので、XSS 自体を出さないことが前提)。

Secure は Cookie を HTTPS 上でしか送らせません。これがないと、たとえ一度でも HTTP でアクセスした瞬間にIDが平文で流れ、サイドジャッキングの餌食になります。さらに Strict-Transport-Security(HSTS)で常時 HTTPS を強制すると、初回の平文アクセス自体を防げます。

SameSite は別サイト発のリクエストにIDを自動付与しない設定で、主に CSRF を緩和します。Lax(多くのブラウザの既定)で書き込み系のクロスサイト送信が止まり、Strict はさらに厳格、None は明示的に別サイト送信を許す代わりに Secure 必須です。

# セッション Cookie の堅牢な設定例(属性を重ねて防御する)
Set-Cookie: __Host-sid=kJ3xQ9...; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1800
#            ^__Host- で Domain 注入を封じ  ^JSから隔離 ^HTTPS限定 ^CSRF緩和   ^短命化
一つの属性に頼らない・絶対値を信じない

HttpOnly は「盗ませない」、Secure は「平文で流さない」、SameSite は「他サイトに付けない」と、守る面が違います。どれか一つでは穴が残ります。さらに、これらは Cookie の運搬を守るだけで、サーバー側の寿命管理(アイドルタイムアウト・絶対タイムアウト・ログアウトでの確実な破棄)とは別問題です。両方を揃えて初めてセッションが安全になります。

実務でのまとめ

原理を踏まえると、堅牢なセッション管理は次の三層に整理できます。

  1. 生成:CSPRNG 由来・128 ビット以上の推測不能なIDを使う。
  2. 運搬HttpOnly; Secure; SameSite を必ず付け、可能なら __Host- 接頭辞と HSTS を併用する。
  3. 寿命:ログイン時にIDを再発行(固定化対策)、アイドル/絶対タイムアウトを設け、ログアウトでサーバー側状態を確実に破棄する。

加えて、特権操作の前には再認証(MFA を含む)を挟み、盗まれたIDで重要操作まで一気に通らない設計にします。フレームワーク標準のセッション機構(Rails・Django・Spring Security 等)はこれらの多くを既定で備えているので、**「切らずに、正しい属性で使う」**のが最短の堅牢化です。

セキュリティ Article

セッション管理の原理(セッション固定・ハイジャック対策)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

セキュリティ

比較で見る軸

難易度: advanced / カテゴリ: セキュリティ / タグ数: 5

導入後に効く点

セッションIDは推測不能が絶対条件。CSPRNG 由来で最低128ビットのエントロピーを持たせ、ログイン直後にIDを再発行(regenerate)してセッション固定攻撃を無効化する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
セキュリティ
タグ数
5

判断チェックリスト

  • 自社の用途が「セキュリティ / セッション」に近いか確認する。
  • 強みである「セッションはサーバ側保持(IDだけ渡す)かステートレス(署名付きトークンに状態を入れる)の二択。前者は失効が即時、後者はスケールしやすいが取り消しが難しいトレードオフがある。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

セキュリティセッションCookie認証セッション固定セキュリティセッションCookie
参考: 公式情報