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')を設定します。