セキュリティ
Cookieのセキュリティを確認する方法:Secure、HttpOnly、SameSiteとCookieプレフィックス
セッションCookieは、訪問者のアカウントを開ける鍵です。Cookieの各属性がそれをどう守るのか、自分のサイトのCookieをどう調べるのか、どう正しく設定するのかを解説します。
このページの内容
誰かがあなたのサイトにログインすると、サーバーは通常、その人のブラウザにセッションCookieを渡します。それ以降は、そのCookieを持っている者がそのユーザーとして扱われます。Cookieの属性は、ブラウザがいつ、どの接続でCookieを送信するか、そしてページ上のスクリプトがそれを読み取れるかどうかを決めます。正しく設定するのに必要なのは設定1行ですが、設定を誤ると、小さなバグがアカウントの乗っ取りに発展しかねません。
重要な属性
Secure
Secure は、CookieをHTTPSでのみ送信するようブラウザに指示します。これがないと、Cookieが暗号化されていないHTTPリクエストに乗って送られることがあります。たとえば、誰かが https:// を付けずにドメインを入力し、リダイレクトが行われる前の時点です。その場合、同じネットワーク上にいる誰もがCookieを読み取れます。HTTPSのサイトでは、すべてのCookieを Secure にすべきです。
HttpOnly
HttpOnly は、CookieをJavaScript(document.cookie)から見えなくします。攻撃者がページにスクリプトを注入できたとしても、セッションCookieを読み取って外部に送るだけ、という手口は使えなくなります。セッションCookieと認証Cookieに指定してください。一部のフレームワークが意図的にJavaScriptへ公開しているCSRFトークンのように、自サイトのフロントエンドのコードが読み取る必要のあるCookieは HttpOnly にできませんが、それは想定どおりの動作です。
SameSite
SameSite は、リクエストが別のサイトから発生したときにCookieを送信するかどうかを制御します。
SameSite=Strict: 自サイト内から始まったリクエストでのみ送信されます。最も安全ですが、メール内のリンクからサイトを訪れた人は、ログアウトしているように見える状態で到着します。SameSite=Lax: リンクのクリックのようなトップレベルのナビゲーションでは送信されますが、クロスサイトのフォーム送信(POST)、画像、フレームでは送信されません。セッションCookieの既定値として適しています。SameSite=None: あらゆるクロスサイトのコンテキストで送信されます。埋め込みウィジェットや一部のシングルサインオンのフローで必要です。必ずSecureと組み合わせなければならず、そうしないとブラウザはCookieを拒否します。
Chromium系のブラウザは、SameSite 属性のないCookieを Lax として扱いますが、すべてのブラウザが同じ動作をするわけではないので、明示的に設定してください。
DomainとPath
Domain を省略すると、Cookieはホスト限定になり、設定したホストそのものにだけ送信されます。Domain=example.com を設定すると、すべてのサブドメインと共有されます。そこには、別のホスティングに置かれたまま忘れられ、セキュリティが甘くなっているかもしれないサブドメインも含まれます。範囲を広げるのは、サブドメイン間でログイン状態を共有する必要が本当にある場合だけにしてください。
ExpiresとMax-Age
Expires も Max-Age もないCookieは、ブラウザのセッションの間だけ有効であることを想定していますが、セッションを復元するブラウザでは、それより長く残ることがあります。ログイン状態を保持する場合は、リスクに見合った有効期間を選び、サーバー側でもセッションを期限切れにするようにしてください。
Cookieプレフィックス:__Host- と __Secure-
Cookie名のプレフィックスを使うと、ブラウザがルールを代わりに強制してくれます。名前が __Secure- で始まるCookieは、Secure 属性があり、かつHTTPS経由で設定された場合にのみ受け入れられます。__Host- で始まるCookieはさらに厳格で、Secure であること、HTTPS経由で設定されること、Path=/ であること、そして Domain 属性がないことが求められます。これによってCookieは1つのホストに固定されるため、侵害されたサブドメインや管理の甘いサブドメインから上書きされることがありません。
サブドメイン間で共有する必要のないセッションCookieには、__Host- が現時点で最も強力な選択肢です: Set-Cookie: __Host-session=…; Secure; HttpOnly; SameSite=Lax; Path=/。
Cookieを確認する方法
- ブラウザで。 開発者ツールを開き、Application(Chrome、Edge)またはStorage(Firefox)からCookiesに進み、自分のサイトを選択します。表には、各CookieのDomain、Path、Expires、HttpOnly、Secure、SameSiteの列が表示されます。
- 生のレスポンスで。 Networkタブでドキュメントのリクエストをクリックし、
Set-Cookieレスポンスヘッダーを読みます。ブラウザが拒否したかもしれない属性も含めて、サーバーが実際に送った内容がそのままわかります。 - ログイン後に。 多くのサイトでは、重要なCookieはサインイン後やカートの作成時に初めて設定されます。そうしたページでもう一度確認してください。
- チェッカーで。 Rudraの無料セキュリティチェッカーは、ページ自体のレスポンスに含まれる
Set-Cookieヘッダーを読み取り、HTTPSなのにSecureのないCookie、セッション用と思われるのにHttpOnlyのないCookie、Secureを伴わないSameSite=None、SameSiteの指定がないCookie、親ドメインをスコープとするCookieを報告します。
自動チェックには、知っておくべき限界が2つあります。まず、Cookieが「セッション用らしい」かどうかは名前から判断しています(sess、sid、token、auth などを含む名前)。そのため、これらの検出結果は信頼度「中」として報告されます。そのCookieが実際に何を保持しているかは、ご自身で確認してください。次に、見えるのはその1つのレスポンスで設定されたCookieだけです。あとからJavaScriptで設定されるCookie、ほかのページやログイン後に設定されるCookie、サードパーティが設定するCookieは対象外です。Cookieの値を読み取ったり保存したりすることは一切ありません。
Cookieの属性をチェック
ページを入力すると、Rudraはそのページが設定するCookieのSecure、HttpOnly、SameSiteの各属性を、名前だけを対象に報告します。HTTPSとヘッダーの設定状況もあわせて確認できます。
主なプラットフォームでCookieの属性を設定する方法
- PHPのセッション:
php.iniでsession.cookie_secure = 1、session.cookie_httponly = 1、session.cookie_samesite = "Lax"(PHP 7.3以降)を設定するか、同じオプションをsession_set_cookie_params()に渡します。 - Django:
SESSION_COOKIE_SECURE = TrueとCSRF_COOKIE_SECURE = Trueを設定します。SESSION_COOKIE_HTTPONLYとSESSION_COOKIE_SAMESITE = "Lax"は、すでに既定値になっています。 - Express (Node.js):
res.cookie("sid", value, { secure: true, httpOnly: true, sameSite: "lax" })とするか、express-sessionのcookieオプションを使います。プロキシの背後で動かす場合は、接続がHTTPSであることをExpressが認識できるようtrust proxyを有効にします。 - WordPress: コアのログインCookieは
HttpOnlyで、サイトがHTTPSで動作していればSecureも付きます。プラグインのCookieはまちまちなので、開発者ツールで確認し、属性が欠けていればプラグインの作者に問い合わせてください。 - 最後の手段としてのリバースプロキシ: nginxの
proxy_cookie_flags(1.19.3以降)を使えば、変更できないアプリケーションのCookieにsecure、httponly、samesiteを追加できます。
よくある間違い
- 本番環境ではCookieを
Secureにしているのに、ローカルではHTTPでしかテストせず、その属性を「一時的に」無効にしてしまう。 SecureなしでSameSite=Noneを設定し、ブラウザにCookieを破棄されて、ログインや埋め込みが動かなくなる。- 「念のため」とセッションCookieのスコープを親ドメインにする。
- セッショントークンを
localStorageに保存する。注入されたスクリプトなら何でも読み取れてしまいます。HttpOnlyのCookieのほうが安全です。 - Cookieの値に個人データを入れる。Cookieが保持すべきなのは識別子であって、情報そのものではありません。
Cookieは全体像の一部にすぎません。ヘッダー、HTTPS、そして受動的なテストの限界については、Webサイトのセキュリティを確認する方法をお読みください。
よくある質問
すべてのCookieをHttpOnlyにすべきですか?
JavaScriptから読み取る必要のないCookieは、すべてそうすべきです。セッションCookieと認証Cookieは、常にHttpOnlyにしてください。フロントエンドのコードが意図的に読み取る設定用のCookieやトークンはHttpOnlyにできませんが、それで問題ありません。
SameSite=LaxだけでCSRFを防げますか?
最も一般的なクロスサイトのフォーム送信(POST)はブロックできますが、それだけで完全な防御にはなりません。同一サイトのサブドメインや、トップレベルのGETリクエストでは、依然としてCookieが送信されます。状態を変更するリクエストにはCSRFトークンを使い続け、GETでデータを変更することは決してしないでください。
SameSite=Noneを設定するとCookieが消えるのはなぜですか?
ブラウザは、Secureが併せて指定されていないSameSite=NoneのCookieを拒否します。Secureを追加し、サイトをHTTPSで配信してください。
RudraはCookieの値を読み取りますか?
いいえ。セキュリティチェッカーが記録するのは、Cookieの名前とその属性だけです。値、トークン、そのほかCookieの中身を読み取ったり、保存したり、表示したりすることは一切ありません。