2026-10-21
Cookieを守る属性 — Secure・HttpOnly・SameSiteを図解する
ログイン状態を保つとき、サーバーはセッションIDをCookieに入れてブラウザに渡します。
つまり、このCookieを盗めば、そのユーザーとしてサイトを操作できます。
前回までの記事で扱ったセキュリティヘッダはレスポンス全体に対する指示でしたが、CookieにはCookie自身に付ける属性があります。この記事では、そのうち守りの要になる3つ、Secure・HttpOnly・SameSiteを図解します。
狙われる3つの経路
まず、Cookieがどこで危ないのかを整理します。
| 経路 | 何が起きるか | 防ぐ属性 |
|---|---|---|
| 通信の盗聴・改ざん | 平文のHTTP通信でCookieが流れる | Secure |
| XSSによる窃取 | 差し込まれたスクリプトがdocument.cookieを読み取って外部に送る | HttpOnly |
| CSRF1 | 別のサイトから、ユーザーのCookie付きでリクエストが飛ぶ | SameSite |
3つとも別々の攻撃なので、1つ付けたからほかは要らない、とはなりません。全部付けるのが基本です。
Set-Cookie: sessionId=38afes7a8; Secure; HttpOnly; SameSite=Lax; Path=/
Secure — HTTPSでしか送らない
Secureを付けたCookieは、HTTPSの通信でしかブラウザから送られません(localhostは例外)。
HSTSの節で、最初のアクセスはHTTPで来ることがある、という話をしました。そのとき、SecureのないCookieは、平文のHTTPリクエストにそのまま乗って流れます。
Secureがあれば、HTTPのリクエストにはそもそもCookieが付かないので、盗聴のしようがありません。
HSTSと目的が重なりますが、役割は違います。HSTSはHTTPに降格させない、Secureは降格されても漏らさないです。二重に守っておけるのが良いところです。
HttpOnly — JavaScriptから読めなくする
HttpOnlyを付けたCookieは、document.cookieで読み出せなくなります。
document.cookie; // HttpOnlyのCookieは含まれない
XSSでスクリプトを差し込まれたとき、攻撃者がやりたい典型的なことは、セッションIDを読み取って自分のサーバーに送ることです。HttpOnlyがあれば、これができません。
ただし、注意点があります。
HttpOnlyが防ぐのはCookieの値を読み取られることで、XSS自体は防ぎません。スクリプトが動いている以上、そのページからfetchを呼べば、ブラウザはCookieを自動で付けて送ります。攻撃者は値を知らなくても、ユーザーのセッションで操作ができてしまいます。
だからHttpOnlyは、あくまで被害を小さくする手段です。XSS対策の本丸は、エスケープとCSPのほうにあります。
SameSite — 別のサイトからのリクエストに付けない
CSRFは、ユーザーがログインしている状態で、攻撃者のページを開かせるところから始まります。攻撃者のページには、ログイン先のサイトへ送信するフォームが仕込んであります。
<!-- 攻撃者のページ -->
<form action="https://my-bank.example/transfer" method="POST">
<input type="hidden" name="recipient" value="attacker">
<input type="hidden" name="amount" value="1000">
</form>
<script>document.forms[0].submit();</script>
ブラウザは、リクエストの送り先に対応するCookieを自動で付けます。送信元が攻撃者のページであっても、お構いなしです。そのため、サーバーはログイン済みのユーザーからの正規のリクエストだと見分けられません。
SameSiteは、リクエストの送信元が別のサイトのときに、Cookieを付けるかどうかを決める属性です。
3つの値
| 値 | 挙動 |
|---|---|
Strict | 同じサイトからのリクエストにだけ付ける |
Lax | 同じサイトに加えて、別のサイトのリンクをクリックして移動したとき(アドレスバーのURLが変わるGETリクエスト)にも付ける |
None | どのリクエストにも付ける。Secureが必須 |
違いは、別のサイトのリンクを踏んで自分のサイトに来た場合に出ます。
Strictだと、リンクで飛んできた直後のリクエストにCookieが付かないので、ログイン済みでもログアウト状態に見えることがあるLaxなら、リンクで飛んできたときはCookieが付くので、ログイン状態が保たれる。一方、攻撃者のページからのフォームPOSTや、画像・iframeの読み込みには付かない
セッションIDのCookieには、Laxを選ぶことが多いです。Strictは強力ですが、外部からのリンクで毎回ログイン状態が切れて見えるのは、使い勝手を大きく損ないます。
なお、SameSiteを省略した場合、最近のブラウザはLaxとして扱います。ただし、POSTを一定時間許すなど、より緩い扱いをする実装もあるので、明示して書くほうが確実です。
sameの範囲は、siteであってoriginではない
名前が紛らわしいところです。SameSiteのsiteは、オリジンより広い単位です。
たとえばhttps://foo.example.orgとhttps://bar.example.orgは、オリジンは別ですが、同じサイトとして扱われます。そのため、サブドメイン間のリクエストには、SameSite=StrictでもCookieが付きます。
サブドメインのどれかが乗っ取られると、ほかのサブドメインにも影響が及びます。この点はSameSiteでは守れません。
Domain、Path、プレフィックス
そのほかにも、Cookieの届く範囲を決める属性があります。
Domain: 付けると、指定したドメインとそのすべてのサブドメインに送られる。省略すると、そのホストにだけ送られる(ホスト限定のCookie)Path: Cookieを送るリクエストのパスを絞る。Path=/docsなら、/docs以下へのリクエストにだけ付く。ただしセキュリティの境界にはならない。ブラウザの区切りはオリジン単位で、同じオリジンならパスが違ってもスクリプトから触れるので、たとえば別のパスのスクリプトが/docsのページをiframeで開けば、そこからdocument.cookieを読めてしまう
Domainを安易に付けると、サブドメイン全体に範囲が広がるので、通常は省略したほうが安全です。
さらに、Cookie名の頭に付けるプレフィックスで、これらの条件をブラウザに強制させられます。
| プレフィックス | ブラウザが強制する条件 |
|---|---|
__Secure- | Secureが付いていて、HTTPSのページから設定されていること |
__Host- | 上に加えて、Path=/であり、Domainを指定していないこと |
Set-Cookie: __Host-sessionId=38afes7a8; Secure; HttpOnly; SameSite=Lax; Path=/
__Host-は、サブドメインから上書きされるのを防げるのが利点です。条件を満たさないCookieは、ブラウザがそもそも受け付けません。
まとめて付けるなら
セッションIDのCookieは、まず次の形にしておけば、基本は押さえられます。
Set-Cookie: __Host-sessionId=...; Secure; HttpOnly; SameSite=Lax; Path=/
SameSite=Noneが必要になるのは、別のサイトに埋め込まれた状態でCookieを使いたい場合に限られます。ログインを持つ外部サービスのiframeなどです。その場合はSecureも必須です。
CSRFについては、SameSiteは多層防御の一枚という位置づけです。CSRFトークンなど、アプリケーション側の対策と併用します。
参照
Footnotes
-
Cross-Site Request Forgery。ログイン済みのユーザーのブラウザを使って、本人の意図しないリクエストを送らせる攻撃。 ↩