2026年8月24日

2026年8月24日

WordPressのCookieにSameSite属性を設定してCSRF対策を強化する方法

WordPressのログインCookieに SameSite 属性を付けると、外部サイトから送られるリクエストにCookieが乗りにくくなり、CSRF(クロスサイトリクエストフォージェリ)の被害を受けにくくなります。ただし SameSite はあくまで補助的な防御で、本命は nonce と権限チェックです。この記事では、WordPressのCookieの現状を確かめる方法、サーバー設定で SameSite を付ける手順、壊れやすい場面、そして nonce と組み合わせた正しい守り方までを順に説明します。

SameSite属性の基本とCSRFとの関係

CSRFは、ログイン中の管理者が攻撃者のページを開いたとき、そのページから自分のサイトへ「設定変更」や「投稿削除」のリクエストを勝手に送らせる攻撃です。ブラウザは通常、送信先のCookieを自動で付けるため、サーバー側からは本人の操作と区別がつきません。

SameSite は「別サイトから始まったリクエストにCookieを付けるかどうか」をブラウザに指示する属性で、値は3種類あります。

  • Strict: 別サイトからのリクエストには一切付けない。外部リンクから管理画面へ移動しても、最初の1回はログインしていない扱いになる
  • Lax: 別サイトからの画像読み込みやフォームのPOSTには付けないが、リンクをクリックしてページを移動する(トップレベルのGET遷移)ときは付ける
  • None: 制限なし。Secure 属性(HTTPSのみ送信)との併用が求められる

Chrome をはじめとする Chromium 系ブラウザは、SameSite が指定されていないCookieを Lax として扱う挙動をすでに導入しています。一方で、すべてのブラウザが同じ既定値とは限らないため、サーバー側で明示しておく意味はあります。

WordPressのCookieの現状を確認する

WordPressコアがログイン時に発行する主なCookieは次のとおりです。

  • wordpress_ または wordpress_sec_ で始まるもの: 管理画面用の認証Cookie(HTTPSでは _sec_ 側)
  • wordpress_logged_in_ で始まるもの: フロント側で「ログイン中」を判定するCookie
  • wordpress_test_cookie、wp-settings- で始まるもの: Cookie動作確認と画面設定の保存用

筆者が確認した範囲では(2026年10月現在)、コアはこれらに SameSite 属性を明示的に付けていません。ただしバージョンアップで変わる可能性もあるため、推測ではなく実際のレスポンスで確かめてください。

確認方法は簡単です。ブラウザの開発者ツールを開いて「ネットワーク」タブを表示した状態で wp-login.php からログインし、POSTリクエストのレスポンスヘッダーにある Set-Cookie を見ます。HttpOnly や secure と並んで SameSite= の記述があるかどうかで判断できます。「アプリケーション」(Firefoxでは「ストレージ」)タブのCookie一覧にも SameSite 列があります。

サーバー設定でSameSiteを付ける手順

コアには認証Cookieの属性を変更する専用フィルターがありません。wp_set_auth_cookie() はプラガブル関数なので上書き自体は可能ですが、コアの更新に追従できなくなる危険が大きいため、ここではWebサーバー側で Set-Cookie ヘッダーを書き換える方法を紹介します。作業前に .htaccess やサーバー設定ファイルのバックアップを取り、可能ならステージング環境で試してください。

Apache(mod_headers)の場合

ドキュメントルートの .htaccess(WordPressのリライトルールの外側)に次を追加します。mod_headers が有効になっている必要があります。

<IfModule mod_headers.c>
  # wordpress_ で始まるCookieにだけ SameSite=Lax を付ける(既に指定があるものは除外)
  Header always edit Set-Cookie "^(wordpress_[^=]*=(?!.*SameSite=).*)$" "$1; SameSite=Lax"
  Header edit Set-Cookie "^(wordpress_[^=]*=(?!.*SameSite=).*)$" "$1; SameSite=Lax"
</IfModule>

2行あるのは、PHPの動かし方(mod_php か PHP-FPM か)によって、PHPが出したヘッダーがどちらの処理対象になるかが異なるためです。正規表現の否定先読みで「既に SameSite があるCookie」は対象外にしているので、両方書いても二重には付きません。

対象を wordpress_ 系に絞っているのは意図的です。すべてのCookieに一律で付けると、後述するように決済やSSOのプラグインが壊れることがあります。

Nginxの場合

Nginx が PHP-FPM に直接つないでいる構成では、FastCGIのレスポンスに含まれるCookieの属性を標準機能だけで書き換える手段は限られます。Nginx をリバースプロキシとして Apache などの前に置いている構成なら、proxy_pass を書いた location で proxy_cookie_flags ディレクティブ(Nginx 1.19.3以降)を使えます。

location / {
    proxy_pass http://backend;
    proxy_cookie_flags ~^wordpress_ samesite=lax;
}

使っているバージョンとモジュール構成で利用可否が変わるため、nginx -t で構文を確認してから反映してください。

PHPのsession.cookie_samesiteは効かない

php.ini の session.cookie_samesite は、PHP標準のセッション(session_start())が発行するCookieにだけ作用します。WordPressコアはPHPセッションを使っていないため、この設定を変えても認証Cookieには影響しません。よく混同される点なので注意してください。

LaxとStrictのどちらを選ぶか

WordPressの認証Cookieには、まず Lax をおすすめします。理由は Strict の副作用が大きいからです。

  • コメント通知メールのリンクから管理画面を開くと、最初の画面だけログアウト状態に見える
  • Slack やチャットツールに貼った編集画面のリンクが、毎回ログイン画面に飛ぶように見える

Lax でも、外部サイトからのフォームPOSTや画像タグ経由のリクエストにはCookieが付かないため、典型的なCSRFの多くは防げます。

壊れやすい場面

次のような機能は、別サイトからのPOSTやiframeでCookieを必要とすることがあります。

  • 決済サービスから自サイトへ戻ってくる処理(外部画面からのPOSTで戻る方式の場合)
  • 外部のIDプロバイダーを使ったSSOやソーシャルログイン
  • 別ドメインのページにiframeで自サイトのログイン状態を埋め込む仕組み

WooCommerce などが発行する独自のCookie(カートのセッション用など)は、上の設定例では対象外にしています。対象を広げる場合は、決済やログインの一連の流れをステージングで通しで試してから本番に反映してください。

SameSiteだけでは足りない理由とnonceの役割

SameSite はブラウザ任せの防御で、次のような抜け道があります。

  • 同一サイト判定はサブドメインを含む: example.com と blog.example.com は「同じサイト」と判定されます。サブドメインのどこかが乗っ取られると、SameSite は効きません
  • 状態を変えるGETリクエストは Lax を通過する: リンクのクリックだけで設定が変わるような作りだと、Lax ではCookieが送られてしまいます
  • 古いブラウザや独自実装のクライアントは属性を解釈しないことがあります

そのため、WordPressでは状態を変える処理に nonce(ワンタイムトークン)と権限チェックを組み合わせるのが基本です。コアの管理画面やREST APIはこの仕組みで守られており、プラグインやテーマで独自のフォームを作るときも同じ方法を使います。

<?php
// フォーム側: nonce フィールドを出力する
function mysite_render_settings_form() {
    ?>
    <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
        <input type="hidden" name="action" value="mysite_save_settings">
        <?php wp_nonce_field( 'mysite_save_settings', 'mysite_nonce' ); ?>
        <input type="text" name="mysite_label" value="<?php echo esc_attr( get_option( 'mysite_label', '' ) ); ?>">
        <?php submit_button( '保存' ); ?>
    </form>
    <?php
}

// 受信側: nonce と権限を確認してから保存する
add_action( 'admin_post_mysite_save_settings', function () {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( '権限がありません。', '', array( 'response' => 403 ) );
    }
    check_admin_referer( 'mysite_save_settings', 'mysite_nonce' );

    $label = isset( $_POST['mysite_label'] )
        ? sanitize_text_field( wp_unslash( $_POST['mysite_label'] ) )
        : '';
    update_option( 'mysite_label', $label );

    wp_safe_redirect( admin_url( 'options-general.php?updated=1' ) );
    exit;
} );

ポイントは3つです。check_admin_referer() は nonce が不正なら処理を止めます。current_user_can() で「その操作をしてよいユーザーか」を確かめます。入力は sanitize_text_field() で整え、出力時は esc_attr() や esc_url() でエスケープします。Ajax なら check_ajax_referer()、REST API ではCookie認証時に X-WP-Nonce ヘッダーで wp_rest の nonce を送るのが標準の方法です。

設定後の確認チェックリスト

設定を入れたら、次の順で確認すると問題を早く見つけられます。

  1. 開発者ツールで、ログイン時の Set-Cookie に SameSite=Lax が1回だけ付いているか
  2. ログアウト、再ログイン、パスワードリセットのメールリンクが正常に動くか
  3. メールやチャットのリンクから管理画面を開いたとき、ログイン状態が保たれるか(Laxなら保たれる)
  4. 決済、会員ログイン、フォーム送信など、外部サービスと行き来する機能が動くか
  5. ページキャッシュやCDNが Set-Cookie を含むレスポンスをキャッシュしていないか

うまくいかないときは、追加した行を一時的にコメントアウトすれば元の状態に戻せます。

まとめ

  • SameSite は「別サイト発のリクエストにCookieを付けるか」をブラウザに指示する属性で、CSRFの被害を減らす補助になる
  • WordPressの認証Cookieには、副作用の少ない Lax を wordpress_ 系に限定して付けるのが無難
  • コアに専用フィルターはないため、Apache の mod_headers などサーバー側で付与し、実際のレスポンスで確認する
  • php.ini の session.cookie_samesite はWordPressの認証Cookieには効かない
  • サブドメインや状態を変えるGETなどの抜け道があるため、nonce と current_user_can() による権限チェックが防御の中心であることは変わらない

お気軽にご相談ください

お見積りへ お問い合わせへ
▲