Весь этот цикл советует одно: личное и живое — отдельным запросом мимо кэша. У этого совета есть обратная сторона. Каждый такой запрос — новый адрес на сайте, открытый всем. И если его не закрыть, он отдаст то, что не должен.
Это 16-я часть цикла «WordPress на кэшируемом сайте».
Случай: заявки с телефонами без входа
На рабочих сайтах стоял внутренний плагин статистики звонков и заявок. Его страница в админке строилась скриптом, который брал данные из REST API плагина. Три адреса: список форм, заявки за период и выгрузка в таблицу Excel. В ответе — телефон, дата и источник рекламы каждой заявки.
Ни у одного адреса не было проверки прав. Анонимный запрос из браузера получал заявки за любой период. Когда мы это нашли, посчитали только число записей, не печатая данных: на шести сайтах за пять недель открыто было от 4 до 181 заявки.
Почему защита не сработала
Защита в плагине была — но написана с ошибкой. Она пыталась закрыть адреса по началу маршрута:
add_filter('rest_pre_dispatch', function ($result, $server, $request) {
// маршрут начинается с /formsstats? — закрыть для анонимов
if (substr($request->get_route(), 0, 6) === '/formsstats') { // 6 символов против 11
// …
}
return $result;
}, 10, 3);
substr(…, 0, 6) возвращает шесть символов, а строка сравнения — одиннадцать. Условие не выполнялось никогда. Защиту написали, проверили, что страница статистики работает, — и не проверили, закрыто ли от анонима.
Как правильно: permission_callback
У REST-маршрутов WordPress проверка прав — обязательный параметр регистрации. С версии 5.5 ядро пишет предупреждение в журнал, если его нет:
register_rest_route('stats/v1', '/forms-data', [
'methods' => 'GET',
'callback' => 'stats_forms_data',
'permission_callback' => fn() => current_user_can('manage_options'),
]);
Грабля после исправления. Страница статистики в админке ходила в REST без заголовка X-WP-Nonce. Без него REST не узнаёт вошедшего администратора даже по куке и отвечает 401 — после закрытия адресов умерла бы и сама страница. Пришлось передать ей nonce: запрос с кукой без nonce — 401, с кукой и nonce — 200.
Попутно: как бот перебирал адреса заявок
Разбираясь, мы увидели в журнале обходчик одной соцсети: за три дня 1005 запросов к 420 разным адресам вида /?post_type=…&p=<номер>. Сами заявки как страницы были закрыты — ответ 404. Но nginx кэшировал эти 404 как варианты главной: точечная чистка главной удалила 449 таких файлов (14-я часть цикла про кэш страниц).
Как проверить свои адреса за 10 минут
- выпишите все анонимные ajax-действия: поиск по коду
wp_ajax_nopriv_; - выпишите свои REST-маршруты:
register_rest_route— и у каждого посмотритеpermission_callback.__return_trueдопустим, только если адрес по смыслу публичный; - для каждого адреса ответьте: что он отдаст анониму и что позволит изменить;
- проверьте не кодом, а запросом без кук:
curl https://сайт/wp-json/…. Ожидаемый ответ закрытого адреса — 401.
Для примера — что открыто анониму на этом сайте и что каждый адрес проверяет:
| Действие | Что отдаёт или меняет | Проверки |
|---|---|---|
| комментарии статьи | одобренные комментарии опубликованной статьи | статья существует и опубликована |
| отправка комментария | создаёт комментарий на модерации | ловушка, время, 3 за 10 минут, без ссылок |
| просмотры и голос | счётчики статьи | статья опубликована, боты не считаются, голос по хэшу адреса |
| заявка | создаёт заявку | ловушка, время, 5 в час, согласие |
| подписка | адрес в статусе «ждёт подтверждения» | ловушка, время, 5 в час, согласие |
Ни один из них не отдаёт данные других людей. Это и есть критерий.
Не уверены, что отдают наружу ajax и REST вашего сайта?
Пришлите адрес — проверю все открытые адреса анонимными запросами и закрою лишнее.
// загружаю комментарии…