cd ~/blog/cookie-security-attributes

2026-10-21

Cookieを守る属性 — Secure・HttpOnly・SameSiteを図解する

CookieHTTPHTTPSXSSCSRFセキュリティ

ログイン状態を保つとき、サーバーはセッションIDをCookieに入れてブラウザに渡します。

つまり、このCookieを盗めば、そのユーザーとしてサイトを操作できます。

前回までの記事で扱ったセキュリティヘッダはレスポンス全体に対する指示でしたが、CookieにはCookie自身に付ける属性があります。この記事では、そのうち守りの要になる3つ、Secure・HttpOnly・SameSiteを図解します。

狙われる3つの経路

まず、Cookieがどこで危ないのかを整理します。

経路何が起きるか防ぐ属性
通信の盗聴・改ざん平文のHTTP通信でCookieが流れるSecure
XSSによる窃取差し込まれたスクリプトがdocument.cookieを読み取って外部に送るHttpOnly
CSRF1別のサイトから、ユーザーのCookie付きでリクエストが飛ぶSameSite

3つとも別々の攻撃なので、1つ付けたからほかは要らない、とはなりません。全部付けるのが基本です。

HTTP
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で読み出せなくなります。

JS
document.cookie; // HttpOnlyのCookieは含まれない

XSSでスクリプトを差し込まれたとき、攻撃者がやりたい典型的なことは、セッションIDを読み取って自分のサーバーに送ることです。HttpOnlyがあれば、これができません。

ただし、注意点があります。

HttpOnlyが防ぐのはCookieの値を読み取られることで、XSS自体は防ぎません。スクリプトが動いている以上、そのページからfetchを呼べば、ブラウザはCookieを自動で付けて送ります。攻撃者は値を知らなくても、ユーザーのセッションで操作ができてしまいます。

だからHttpOnlyは、あくまで被害を小さくする手段です。XSS対策の本丸は、エスケープとCSPのほうにあります。

SameSite — 別のサイトからのリクエストに付けない

CSRFは、ユーザーがログインしている状態で、攻撃者のページを開かせるところから始まります。攻撃者のページには、ログイン先のサイトへ送信するフォームが仕込んであります。

HTML
<!-- 攻撃者のページ -->
<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を指定していないこと
HTTP
Set-Cookie: __Host-sessionId=38afes7a8; Secure; HttpOnly; SameSite=Lax; Path=/

__Host-は、サブドメインから上書きされるのを防げるのが利点です。条件を満たさないCookieは、ブラウザがそもそも受け付けません。

まとめて付けるなら

セッションIDのCookieは、まず次の形にしておけば、基本は押さえられます。

HTTP
Set-Cookie: __Host-sessionId=...; Secure; HttpOnly; SameSite=Lax; Path=/

SameSite=Noneが必要になるのは、別のサイトに埋め込まれた状態でCookieを使いたい場合に限られます。ログインを持つ外部サービスのiframeなどです。その場合はSecureも必須です。

CSRFについては、SameSiteは多層防御の一枚という位置づけです。CSRFトークンなど、アプリケーション側の対策と併用します。

参照

Footnotes

  1. Cross-Site Request Forgery。ログイン済みのユーザーのブラウザを使って、本人の意図しないリクエストを送らせる攻撃。 ↩