Весь этот цикл советует одно: личное и живое — отдельным запросом мимо кэша. У этого совета есть обратная сторона. Каждый такой запрос — новый адрес на сайте, открытый всем. И если его не закрыть, он отдаст то, что не должен.

Это 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 минут

  1. выпишите все анонимные ajax-действия: поиск по коду wp_ajax_nopriv_;
  2. выпишите свои REST-маршруты: register_rest_route — и у каждого посмотрите permission_callback. __return_true допустим, только если адрес по смыслу публичный;
  3. для каждого адреса ответьте: что он отдаст анониму и что позволит изменить;
  4. проверьте не кодом, а запросом без кук: curl https://сайт/wp-json/…. Ожидаемый ответ закрытого адреса — 401.

Для примера — что открыто анониму на этом сайте и что каждый адрес проверяет:

ДействиеЧто отдаёт или меняетПроверки
комментарии статьиодобренные комментарии опубликованной статьистатья существует и опубликована
отправка комментариясоздаёт комментарий на модерацииловушка, время, 3 за 10 минут, без ссылок
просмотры и голоссчётчики статьистатья опубликована, боты не считаются, голос по хэшу адреса
заявкасоздаёт заявкуловушка, время, 5 в час, согласие
подпискаадрес в статусе «ждёт подтверждения»ловушка, время, 5 в час, согласие

Ни один из них не отдаёт данные других людей. Это и есть критерий.

Не уверены, что отдают наружу ajax и REST вашего сайта?

Пришлите адрес — проверю все открытые адреса анонимными запросами и закрою лишнее.

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

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

Комментарии

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

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