2026年5月20日

2026年5月20日

WordPressのCSRF(クロスサイトリクエストフォージェリ)対策をする方法

はじめに

CSRF(クロスサイトリクエストフォージェリ)は、攻撃者が被害者のログインセッションを悪用して、意図しないリクエストを送信させる攻撃です。WordPressのnonce(ワンタイムトークン)システムを正しく実装することで防御できます。

症状・原因

  • フォーム送信やAJAXリクエストにCSRF対策が実装されていない
  • セキュリティ診断でCSRF脆弱性が指摘された
  • カスタムフォームにwp_nonce_field()が設置されていない
  • 管理画面のカスタム処理でcheck_admin_referer()を呼んでいない

解決手順

ステップ1:WordPress のnonce システムを理解する

// WordPress の nonce は「Number used ONCE」の略
// セッション固有のトークンで、有効期間は12〜24時間
// 同一ユーザー・同一アクション・同一セッションでのみ有効

// ① nonce の生成
$nonce = wp_create_nonce('my_action');
// → ランダムな文字列(例: 'a7b3f4c2d1')

// ② nonce の検証
$verified = wp_verify_nonce($_POST['nonce'], 'my_action');
// → 1: 有効(12時間以内)
// → 2: 有効(12〜24時間、期限近い)
// → false: 無効(改ざん・期限切れ・別ユーザー)

// ③ nonce の有効期限と用途
// wp_nonce_field(): HTMLフォーム用(hidden input を生成)
// wp_create_nonce(): JavaScript/AJAX 用
// check_admin_referer(): 管理画面フォーム用(失敗時に自動的にwp_die)
// check_ajax_referer(): AJAX 用(失敗時に自動的にdie)

ステップ2:HTMLフォームにnonceを実装する

// フロントエンドのカスタムフォームに nonce を追加

// ① フォームに nonce フィールドを追加(テンプレートファイル)
?>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
    <?php wp_nonce_field('contact_form_action', 'contact_nonce'); ?>
    <input type="hidden" name="action" value="submit_contact">
    <input type="text" name="name" required>
    <input type="email" name="email" required>
    <textarea name="message" required></textarea>
    <button type="submit">送信</button>
</form>
<?php

// ② フォーム処理側で nonce を検証
add_action('admin_post_submit_contact', 'handle_contact_form');
add_action('admin_post_nopriv_submit_contact', 'handle_contact_form');

function handle_contact_form(): void {
    // nonce を検証(失敗時はwp_die)
    if (!isset($_POST['contact_nonce'])
        || !wp_verify_nonce($_POST['contact_nonce'], 'contact_form_action')) {
        wp_die('不正なリクエストです。', 'セキュリティエラー', ['response' => 403]);
    }

    // 検証後に処理
    $name    = sanitize_text_field($_POST['name'] ?? '');
    $email   = sanitize_email($_POST['email'] ?? '');
    $message = sanitize_textarea_field($_POST['message'] ?? '');

    // 処理後にリダイレクト
    wp_safe_redirect(home_url('/contact/?sent=1'));
    exit;
}

ステップ3:AJAX リクエストに nonce を実装する

// functions.php: AJAX + nonce の実装

// ① JavaScript に nonce を渡す
add_action('wp_enqueue_scripts', function(): void {
    wp_enqueue_script('my-ajax', get_template_directory_uri() . '/js/ajax.js', ['jquery'], '1.0', true);
    wp_localize_script('my-ajax', 'myAjax', [
        'ajaxurl' => admin_url('admin-ajax.php'),
        'nonce'   => wp_create_nonce('my_ajax_nonce'),
    ]);
});

// ② AJAX ハンドラー側で nonce を検証
add_action('wp_ajax_my_action', 'handle_my_ajax');
add_action('wp_ajax_nopriv_my_action', 'handle_my_ajax');

function handle_my_ajax(): void {
    // nonce を検証(失敗時は die)
    check_ajax_referer('my_ajax_nonce', 'nonce');

    // 処理
    $data = sanitize_text_field($_POST['data'] ?? '');
    wp_send_json_success(['result' => $data]);
}
// js/ajax.js: jQuery AJAX で nonce を送信
jQuery(function ($) {
    $('#my-form').on('submit', function (e) {
        e.preventDefault();
        $.post(myAjax.ajaxurl, {
            action: 'my_action',
            nonce: myAjax.nonce,        // nonce を送信
            data: $('#my-input').val()
        }, function (response) {
            if (response.success) {
                console.log(response.data);
            }
        });
    });
});

ステップ4:管理画面のカスタム処理に nonce を追加する

// functions.php: 管理画面のカスタムフォームに nonce を追加

// ① 設定ページのフォームに nonce を追加
add_action('admin_menu', function(): void {
    add_menu_page('My Settings', 'My Settings', 'manage_options', 'my-settings', 'my_settings_page');
});

function my_settings_page(): void {
    // 保存処理
    if (isset($_POST['save_settings'])) {
        // 管理画面用 nonce チェック(失敗時は自動的に wp_die)
        check_admin_referer('my_settings_action', 'my_settings_nonce');

        update_option('my_setting_key', sanitize_text_field($_POST['my_setting'] ?? ''));
        echo '<div class="notice notice-success"><p>設定を保存しました。</p></div>';
    }

    // フォーム表示
    $current_value = get_option('my_setting_key', '');
    ?>
    <div class="wrap">
        <h1>My Settings</h1>
        <form method="post">
            <?php wp_nonce_field('my_settings_action', 'my_settings_nonce'); ?>
            <table class="form-table">
                <tr>
                    <th>設定値</th>
                    <td><input type="text" name="my_setting" value="<?php echo esc_attr($current_value); ?>"></td>
                </tr>
            </table>
            <?php submit_button('保存', 'primary', 'save_settings'); ?>
        </form>
    </div>
    <?php
}

ステップ5:REST API に nonce を設定する

// functions.php: REST API に CSRF 対策を追加

// ① JavaScript に REST nonce を渡す
add_action('wp_enqueue_scripts', function(): void {
    wp_localize_script('my-script', 'wpApiSettings', [
        'root'  => esc_url_raw(rest_url()),
        'nonce' => wp_create_nonce('wp_rest'),  // REST API 専用 nonce
    ]);
});
// REST API リクエスト時に X-WP-Nonce ヘッダーを送信
fetch('/wp-json/wp/v2/posts', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': wpApiSettings.nonce  // CSRF 対策
    },
    body: JSON.stringify({ title: 'New Post', status: 'draft' })
});

注意事項

  • wp_nonce_field()が生成するフィールド名はデフォルトで_wpnonceです。独自の名前を指定する場合は第2引数で指定してください
  • nonceの有効期限は12〜24時間です。長時間開きっぱなしのフォームでは期限切れが起こる場合があります。AJAXで定期的にnonceをリフレッシュするか、wp_nonce_tickフィルターで有効期間を調整してください
  • check_admin_referer()check_ajax_referer()は失敗時に自動的に処理を停止(wp_die())します。独自のエラーハンドリングが必要な場合はwp_verify_nonce()を直接使用してください

まとめ

CSRF対策は①HTMLフォームにwp_nonce_field('action', 'field_name')でnonceフィールドを追加、②処理側でwp_verify_nonce($_POST['field_name'], 'action')またはcheck_admin_referer()で検証、③AJAX はwp_create_nonce()で生成してPOSTデータに含めcheck_ajax_referer()で検証、④REST APIはX-WP-Nonceヘッダーにwp_create_nonce('wp_rest')を設定します。

お気軽にご相談ください

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