Takeo

Takeo

この記事の目次

Cookie・セッション管理シリーズ 第3回 / 全5回

Cookieのセキュリティ設計:HttpOnly・Secure・SameSiteを理解する

第1回ではCookieによる状態管理、第2回ではCookieの有効期限を解説した。今回はCookieを認証に使うとき、何を守る必要があるのかを考える。

CookieはブラウザがHTTPリクエストに自動で添付できる便利な仕組みだ。しかし、保存された認証情報が盗まれたり、意図しないリクエストに添付されたりすると、攻撃の入口になる。

この記事では、サーバー側にセッションを持つWebアプリケーションを基本例とする。

1. まず守るべきものを整理する

ここで重要なのは、Cookie内のsession_idは本人確認の手掛かりとなる秘密情報だという点だ。セッションIDを盗まれれば、サーバーがそのIDを有効と判断する限り、第三者にログイン状態を悪用される可能性がある。

主な脅威は次の3つ。

脅威何が起きるか主な対策
Cookieの盗難攻撃者がセッションを悪用するHttpOnly、Secure、XSS対策、セッション失効
意図しないCookie送信別サイトから操作を実行させられるSameSite、CSRFトークン、Origin検証
Cookieの改ざんCookieの値を書き換えて権限を偽装するサーバー側検証、署名、適切な認可

Cookie属性は防御の一層にすぎない。属性を設定するだけでXSSやCSRFが完全に防げるわけではない。

2. 安全なCookie設定の基本

認証用Cookieの例を見てみよう。

Copied!!
HTTP/1.1 200 OK
Set-Cookie: __Host-session=RANDOM_SESSION_ID; Path=/; Max-Age=3600; Secure; HttpOnly; SameSite=Lax
属性意味注意点
HttpOnlyJavaScriptのdocument.cookieから読み取れなくするXSSそのものは防がない。悪意あるスクリプトによる認証済み操作も防げない
SecureHTTPS接続でのみ送信するHTTPSの採用やHSTSの代わりではない
SameSite=Lax一部のクロスサイトリクエストではCookieを送らないトップレベル遷移の安全なメソッドなどでは送信され得る
SameSite=Strictクロスサイト起点のリクエストでは基本的に送信しない外部リンクから戻る場合などにログイン状態が反映されないことがある
SameSite=Noneクロスサイト送信を許可するSecureが必要。第三者Cookie制限の影響も受ける
Path=//配下を送信対象とするセキュリティ境界にはならない
Max-Age=3600Cookieの寿命を3600秒にするサーバー側セッションの寿命とは独立

__Host-という接頭辞

__Host-付きCookieを対応ブラウザが受理するには、Secure、Path=/、Domain属性なし、という条件を満たす必要がある。これにより、サブドメインから親ドメイン向けの同名Cookieを設定するリスクを抑えられる。

DomainとPathはCookieの送信範囲を決める属性であり、アクセス権限を保証する仕組みではない。

3. XSS:悪意あるJavaScriptが実行されたら?

XSS(Cross-Site Scripting)は、攻撃者のスクリプトが利用者の閲覧するページ上で実行される脆弱性だ。

HttpOnlyはCookieの直接的な読み取りを制限する。しかし、スクリプトが同じサイト上で動いてしまえば、ブラウザがCookieを自動添付するリクエストを送れることがある。したがって、HttpOnlyだけでは十分ではない。

XSSの予防には、出力時エスケープ、危険なHTML挿入の回避、入力の適切な処理、Content Security Policy(CSP)などを組み合わせる。

4. CSRF:なぜ別サイトから操作できてしまうのか

CSRF(Cross-Site Request Forgery)は、利用者がログイン済みであることを利用して、別サイトから意図しない操作を行わせる攻撃だ。

SameSiteは、クロスサイト起点のリクエストにCookieを添付するかを制御する。一般的な認証CookieではLaxを出発点に、要件に応じてStrictを検討するとよい。

ただし、SameSiteだけに依存しないことが重要だ。状態変更APIにはCSRFトークンやOriginヘッダーの検証を組み合わせ、GETで状態変更しない設計にする。SameSiteの「site」とJavaScriptの同一オリジン判定の「origin」は同じ概念ではない点にも注意したい。

5. Cookieは書き換えられる:改ざん対策

ブラウザに保存されたCookieは、利用者側で変更できるものとして設計すべきだ。

悪い例:role=adminというCookieの値だけを信頼して管理者権限を付与する。

基本形:Cookieには推測困難なランダムセッションIDを保存し、権限はサーバー側で確認する。

Cookie自体にデータを保存する場合は、署名(完全性の検証)や必要に応じた暗号化を検討する。ただし署名は暗号化ではない。署名付きデータは内容を読める場合がある。

6. セッションIDとJWTをCookieに入れる場合の違い

比較項目ランダムなセッションIDJWT
Cookie内の値推測困難な識別子署名付きのクレームなどを含むトークン
サーバー側での検証セッションストアへの照会が一般的署名・exp・iss・audなどを検証
権限変更・失効セッション削除で反映しやすい自己完結型JWTは即時失効に追加設計が必要
盗難時のリスク有効期間中は悪用され得る有効期間中は悪用され得る

JWTをCookieに入れても、HttpOnly・Secure・SameSiteの検討は必要だ。JWTのexpとCookieのMax-Ageは別の期限なので、両方を整合させる。JWTは署名されていても、通常のJWS形式ではペイロードが暗号化されていない。

7. ログアウトはCookie削除だけで十分?

結論から言えば、サーバー側セッション方式ではCookie削除に加えてサーバー側セッションも失効させるのが基本だ。

Cookieを削除するときは、通常、設定時と同じCookie名・Path・Domainの組み合わせを対象にする。__Host- CookieではDomainを付けない。削除のためのMax-Age=0は即時失効を指示する。

JWTなどの自己完結型トークンでは、Cookie削除だけで盗難済みトークンを無効化できない。短い有効期限、失効リスト、トークンのローテーションなど、要件に応じた対策が必要になる。

8. Chrome DevToolsで確認してみる

  1. 開発者ツールを開き、Application → Storage → Cookiesを選ぶ。
  2. 対象サイトのCookieを確認し、HttpOnly・Secure・SameSite・Expires / Max-Ageに相当する表示を探す。
  3. Networkタブでログイン時のレスポンスを選び、Set-Cookieを確認する。
  4. 同一サイトへの後続リクエストで、送信されるCookieヘッダーを確認する。
  5. HttpOnlyの付いたCookieがdocument.cookieに表示されないことを確認する(DevToolsのCookie一覧には表示され得る)。

実験は自分で管理する検証環境で行うこと。認証Cookieの値をスクリーンショットやログで公開しないよう注意する。

9. 実務向けチェックリスト

  • サイト全体をHTTPS化し、必要に応じてHSTSを設定する
  • 認証CookieにSecureとHttpOnlyを付ける
  • 用途に応じたSameSiteを明示する
  • 可能なら__Host-接頭辞を採用する
  • セッションIDは十分にランダムな値とし、ログイン時に再生成する
  • Cookie内の権限情報を無条件に信頼しない
  • 状態変更APIでCSRF対策とサーバー側認可を行う
  • XSS対策をCookie属性とは独立して実施する
  • Cookie期限とサーバー側セッション期限を設計する
  • ログアウト時にサーバー側の認証状態も失効させる

まとめ

Cookieのセキュリティは、単一の属性で完結しない。

  • HttpOnly:JavaScriptによるCookieの直接読み取りを制限する。
  • Secure:Cookieの送信をHTTPSに限定する。
  • SameSite:クロスサイトリクエストへのCookie添付を制御する。
  • サーバー側の検証・失効:Cookieを持っているだけで無条件に権限を与えない。

重要なのは、Cookieを送るブラウザ側のルールと、認証・認可を判断するサーバー側のルールを分けて考えることだ。

参考資料