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
| 属性 | 意味 | 注意点 |
|---|---|---|
HttpOnly | JavaScriptのdocument.cookieから読み取れなくする | XSSそのものは防がない。悪意あるスクリプトによる認証済み操作も防げない |
Secure | HTTPS接続でのみ送信する | HTTPSの採用やHSTSの代わりではない |
SameSite=Lax | 一部のクロスサイトリクエストではCookieを送らない | トップレベル遷移の安全なメソッドなどでは送信され得る |
SameSite=Strict | クロスサイト起点のリクエストでは基本的に送信しない | 外部リンクから戻る場合などにログイン状態が反映されないことがある |
SameSite=None | クロスサイト送信を許可する | Secureが必要。第三者Cookie制限の影響も受ける |
Path=/ | /配下を送信対象とする | セキュリティ境界にはならない |
Max-Age=3600 | Cookieの寿命を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に入れる場合の違い
| 比較項目 | ランダムなセッションID | JWT |
|---|---|---|
| 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で確認してみる
- 開発者ツールを開き、Application → Storage → Cookiesを選ぶ。
- 対象サイトのCookieを確認し、
HttpOnly・Secure・SameSite・Expires / Max-Ageに相当する表示を探す。 - Networkタブでログイン時のレスポンスを選び、
Set-Cookieを確認する。 - 同一サイトへの後続リクエストで、送信される
Cookieヘッダーを確認する。 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を送るブラウザ側のルールと、認証・認可を判断するサーバー側のルールを分けて考えることだ。
