В форме заявки на этом сайте нет токена WordPress (nonce). Это не забывчивость: страница с формой лежит в кэше неделю, а токен живёт сутки. Вместо него — три простые проверки, которые с кэшем не спорят.

Это девятая часть цикла «WordPress на кэшируемом сайте» — первая из пяти про формы.

Почему токен в кэшированной форме — бомба

wp_create_nonce выдаёт строку, которая принимается около суток. Если она напечатана в разметке, а разметка живёт в кэше дольше, посетитель получает форму с мёртвым токеном и заявка отклоняется. На одном из рабочих сайтов мы нашли 6 мёртвых токенов из 18 закэшированных форм — разбор и решение с выдачей токена отдельным запросом в 17-й части цикла про кэш фрагментов.

От чего nonce на самом деле защищает

Nonce в WordPress — защита от подделки запроса от имени вошедшего пользователя: чтобы чужой сайт не мог заставить браузер администратора что-то удалить. Токен привязан к пользователю и его сессии.

У анонимного посетителя ни пользователя, ни сессии нет, поэтому токен для всех анонимов в течение 12 часов один и тот же. Бот, которому нужна ваша форма, просто читает его со страницы, как и браузер. От спама анонимный nonce не защищает вовсе — он только добавляет способ потерять настоящую заявку.

Где nonce нужен: действия вошедших пользователей и всё в админке. Где не нужен: анонимная форма заявки, подписки, комментария.

Что вместо него

  • поле-ловушка. Невидимое поле «сайт»: человек его не видит, бот заполняет;
  • время заполнения. Скрипт считает секунды с открытия страницы и отправляет их вместе с формой. Меньше 3 секунд или поле не пришло (отправка без скрипта) — не человек;
  • лимит с адреса. Не больше 5 заявок в час с одного IP.
$elapsed = isset($_POST['elapsed']) ? (int) $_POST['elapsed'] : -1;
if ($field('website', 200) !== '' || $elapsed < 3) {
    // Бот: отвечаем «успешно», чтобы не подсказывать, что именно его выдало.
    wp_send_json(['ok' => true]);
}

$limitKey = 'contact_form_rl_' . md5($ip);
$sent = (int) get_transient($limitKey);
if ($sent >= 5) {
    wp_send_json(['ok' => false, 'message' => 'слишком много заявок подряд, напишите в Telegram']);
}

Настоящий адрес посетителя за прокси — первый в X-Forwarded-For, иначе лимит считался бы по адресу прокси, одному на всех.

Почему этого хватает

Массовый спам по формам идёт скриптами без браузера: они заполняют все поля подряд и отправляют сразу. На такие отправки ловушка и время срабатывают надёжно. Тот, кто вручную пишет бота под конкретный сайт, обойдёт и капчу; лимит с адреса ограничит ущерб.

Главное — ни одна из трёх проверок не зависит от того, когда страница попала в кэш. Форма из кэша недельной давности отправляется так же, как свежая.

Заявка не сбрасывает кэш

Мелочь, которую легко пропустить: заявки у нас — отдельный тип записи, и их сохранение по умолчанию попало бы в общий сброс кэша на save_post. Заявка — не контент сайта, поэтому для неё сброс снят:

// save_post_{тип} срабатывает раньше save_post — снятие успевает
add_action('save_post_lead', function () {
    remove_action('save_post', 'cache_purge_on_save_post');
}, 1);

На одном из рабочих сайтов этого не было, и каждая заявка с формы сбрасывала кэш главной.

Заявки теряются, а в журнале про токен?

Пришлите адрес — уберу токен из кэшированной разметки и проверю, что форма отправляется всегда.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.