☰
WORDPRESS / PRACTICE

Nonce и capabilities в WordPress: как правильно защищать действия

Nonce и capabilities в WordPress: как правильно защищать действия

В WordPress nonce часто воспринимают как универсальную защиту формы или AJAX-запроса. Это опасное заблуждение: nonce подтверждает, что запрос, вероятно, сформирован в правильном контексте, но не доказывает, что пользователь имеет право выполнять действие.

Надёжная проверка состоит как минимум из нескольких уровней: аутентификация пользователя, проверка nonce, проверка capability, валидация и санитизация данных. Только после этого код должен изменять настройки, записи, профили или другие объекты.

Что такое nonce и от чего он защищает

Nonce в WordPress — это короткоживущий токен, связанный с пользователем и контекстом действия. Его назначение — снизить риск CSRF-атаки, при которой сторонний сайт пытается заставить уже авторизованного пользователя выполнить нежелательный запрос в WordPress.

Например, nonce можно создать для действия update_profile, добавить в форму или передать JavaScript-коду, а при обработке проверить его через check_admin_referer() или check_ajax_referer(). Название действия должно быть конкретным, чтобы токен без необходимости не использовался для других операций.

Однако nonce не является паролем, не заменяет вход пользователя в систему и не определяет его роль или разрешения. Если nonce утёк или запрос выполняет пользователь, у которого уже есть доступ к странице, сама проверка nonce не остановит несанкционированное изменение.

  • Nonce в первую очередь защищает от CSRF.
  • Nonce не предоставляет права на редактирование данных.
  • Nonce не заменяет аутентификацию и авторизацию.
  • Nonce не следует рассматривать как секрет, предназначенный для длительного хранения или передачи между разными действиями.

Capabilities определяют, что может делать пользователь

Capabilities — это разрешения WordPress, которые описывают доступ к конкретным операциям. Среди них, например, manage_options, edit_posts, publish_posts, delete_posts, edit_users и edit_user. Проверять нужно не название роли, а capability, соответствующую конкретному действию.

Проверка current_user_can('administrator') — неправильный подход. administrator — это название роли, а не capability. Кроме того, проверка роли плохо масштабируется: на сайте могут использоваться собственные роли, мультисайтовая среда или пользователи с точно определённым набором разрешений.

Для объектов важно передавать правильный идентификатор. Например, для редактирования записи проверяют edit_post с ID записи, а для изменения пользователя — соответствующее разрешение с ID пользователя. Это позволяет WordPress учитывать не только общую роль, но и конкретный объект и его автора.

Capability должна соответствовать минимально необходимому уровню доступа. Если действие изменяет одну опцию плагина, не нужно требовать чрезмерно широкие права вроде manage_options, если архитектура плагина позволяет создать или использовать отдельное разрешение.

  • Для доступа к конкретной записи используйте object-specific capability, например edit_post.
  • Не проверяйте роль по названиям administrator, editor или другим строкам.
  • Не полагайтесь на скрытие кнопки в интерфейсе: серверная проверка обязательна.
  • Применяйте принцип наименьших привилегий — пользователь должен получать только необходимые разрешения.

Правильный порядок проверок в запросе

Для AJAX, admin-post.php и собственных REST-обработчиков проверки должны выполняться последовательно. Сначала проверьте nonce и наличие необходимой аутентификации, затем capability, после этого получите и проверьте данные. Изменение базы данных выполняйте только после успешного прохождения всех проверок.

Nonce лучше проверять до сложной обработки запроса. Capability необходимо проверять на сервере, даже если нужная кнопка видна только администраторам. Пользователь может вручную сформировать POST-запрос и обойти любое ограничение интерфейса.

Ниже приведён упрощённый пример для AJAX-действия. Конкретная capability зависит от того, чьи данные изменяет обработчик и какие правила доступа нужны в вашем плагине.

  • Проверьте nonce для конкретного действия.
  • Убедитесь, что пользователь аутентифицирован, если действие не должно быть доступно гостям.
  • Проверьте capability с правильным ID объекта.
  • Удалите слеши из данных, полученных через POST или GET.
  • Санитизируйте, валидируйте и нормализуйте значения.
  • Выполняйте операцию записи только после всех проверок.
check_ajax_referer( 'update_profile', 'nonce' );

$user_id = get_current_user_id();

if ( ! $user_id ) {
    wp_send_json_error( 'Потрібна авторизація', 401 );
}

if ( ! current_user_can( 'edit_user', $user_id ) ) {
    wp_send_json_error( 'Недостатньо прав', 403 );
}

$email = sanitize_email(
    wp_unslash( $_POST['email'] ?? '' )
);

if ( ! is_email( $email ) ) {
    wp_send_json_error( 'Некоректний email', 400 );
}

// Виконуйте зміну лише після всіх перевірок.

Как проверять данные после nonce и capability

Надёжный nonce не делает входные данные достоверными. Пользователь может передать некорректный email, неожиданное число, запрещённое значение статуса или ID чужого объекта. Поэтому каждое поле нужно обрабатывать с учётом его типа и назначения.

Для текста применяйте соответствующую санитизацию, но не путайте её с валидацией. sanitize_email() очищает значение, а is_email() проверяет, похоже ли оно на корректный email-адрес. Для числового ID можно использовать absint(), но этого недостаточно: после получения ID всё равно нужно проверить, что объект существует и пользователь имеет доступ именно к нему.

Для списков разрешённых значений полезно использовать белый список. Например, статус можно принимать только тогда, когда он соответствует одному из заранее определённых значений. Не следует передавать в SQL необработанные значения или полагаться на то, что поле формы уже имеет правильный формат.

Данные из $_POST и $_GET в WordPress часто нужно обрабатывать через wp_unslash(), поскольку среда может добавлять слеши. После этого применяйте нужную санитизацию или валидацию.

  • Определите ожидаемый тип каждого поля.
  • Используйте санитизацию для очистки, а валидацию — для проверки допустимости.
  • Проверяйте числовые диапазоны, длину строк и список разрешённых значений.
  • Для ID проверяйте не только формат, но и право доступа к соответствующему объекту.
  • Не выводите необработанные данные в HTML, JavaScript или SQL.
$post_id = absint( $_POST['post_id'] ?? 0 );
$status  = sanitize_key( wp_unslash( $_POST['status'] ?? '' ) );

if ( ! $post_id || ! get_post( $post_id ) ) {
    wp_send_json_error( 'Об’єкт не знайдено', 404 );
}

if ( ! current_user_can( 'edit_post', $post_id ) ) {
    wp_send_json_error( 'Недостатньо прав', 403 );
}

$allowed_statuses = array( 'draft', 'publish' );

if ( ! in_array( $status, $allowed_statuses, true ) ) {
    wp_send_json_error( 'Недозволене значення', 400 );
}

Особенности AJAX, admin-post и REST API

Для AJAX в WordPress обычно используют check_ajax_referer(). При недействительном nonce эта функция может завершить выполнение с ошибкой, поэтому последующий код не должен выполняться. Важно также разделять обработчики для авторизованных и неавторизованных пользователей: само наличие AJAX-маршрута не означает, что гость должен получить доступ к действию.

Для форм, отправляемых через admin-post.php, применяйте check_admin_referer() с названием действия. В таком обработчике также проверяйте capability, поскольку nonce лишь подтверждает контекст формы.

В REST API nonce часто используется для запросов из панели администратора, но REST-эндпоинт должен иметь собственный permission_callback. Именно этот callback должен определять, разрешено ли текущему пользователю выполнять операцию. Не оставляйте permission_callback формальным или таким, который всегда возвращает true, если эндпоинт изменяет данные.

  • AJAX: проверяйте nonce и capability в callback действия.
  • admin-post.php: используйте check_admin_referer() и отдельную проверку прав.
  • REST API: реализуйте проверку доступа в permission_callback.
  • Не считайте URL или название action секретным механизмом защиты.
  • Возвращайте корректные ошибки и HTTP-статусы, не раскрывая лишние внутренние детали.
add_action( 'admin_post_update_setting', 'my_update_setting' );

function my_update_setting() {
    check_admin_referer( 'update_setting' );

    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'Недостатньо прав', 403 );
    }

    $value = sanitize_text_field(
        wp_unslash( $_POST['value'] ?? '' )
    );

    // Перевірка допустимих значень і збереження налаштування.
}

Распространённые ошибки и способы их исправления

Самая частая ошибка — проверять только nonce. Такой код защищает от части CSRF-сценариев, но не запрещает авторизованному пользователю изменить чужую запись, если capability не проверяется с конкретным ID.

Другая проблема — проверять capability только при выводе формы. Кнопка может быть скрыта, но запрос всё равно можно отправить вручную. Любое правило доступа должно выполняться в серверном обработчике.

Опасно также использовать одно общее nonce-действие для множества разных операций или принимать ID объекта от клиента без проверки права доступа. Значение, переданное пользователем, не доказывает, что объект ему принадлежит.

Слишком широкие разрешения создают дополнительный риск. Если для изменения одной записи используется manage_options или проверка администратора, ошибка в коде может открыть доступ к гораздо большему объёму данных, чем необходимо.

  • Ошибка: nonce считают авторизацией. Решение: добавить current_user_can().
  • Ошибка: проверяют роль вместо capability. Решение: проверять конкретное разрешение.
  • Ошибка: контролируют доступ только в интерфейсе. Решение: повторить проверку на сервере.
  • Ошибка: доверяют ID из запроса. Решение: проверить существование объекта и право доступа к нему.
  • Ошибка: сохраняют данные без валидации. Решение: определить формат и допустимые значения каждого поля.

Практический чеклист перед релизом

Перед публикацией плагина или темы протестируйте каждое действие не только под учётной записью администратора. Проверьте сценарии для неавторизованного пользователя, обычного пользователя, пользователя с ограниченной ролью и пользователя, который пытается работать с чужим объектом.

Отдельно протестируйте повторную отправку запроса, подмену ID, отсутствующие поля, пустые значения, неправильный тип данных и недействительный nonce. Защита должна работать независимо от того, пришёл ли запрос из вашего JavaScript, через браузерную форму или от вручную сформированного HTTP-клиента.

  • Для каждого действия определено отдельное nonce-действие.
  • Nonce проверяется в серверном обработчике.
  • Capability проверяется непосредственно перед операцией.
  • Для объектов используются правильный ID и object-specific capability.
  • Все внешние данные проходят unslash, санитизацию и валидацию.
  • Гости не получают доступ к приватным операциям без специально предусмотренной логики.
  • Ошибки не раскрывают пароли, токены, SQL-запросы или внутреннюю структуру системы.
  • Проверен доступ с разными ролями и к чужим объектам.

Частые вопросы

Достаточно ли nonce для защиты формы в WordPress?

Нет. Nonce в первую очередь защищает от CSRF, но не определяет права пользователя. Для действий, изменяющих данные, также нужны аутентификация, проверка capability и валидация входных значений.

Можно ли проверять роль administrator вместо capability?

Не рекомендуется. Роли могут отличаться на разных сайтах, а проверка роли плохо описывает конкретное действие. Используйте current_user_can() с нужной capability, например edit_post, delete_post или manage_options.

Где проверять capability — в JavaScript или PHP?

Обязательно в PHP на сервере. JavaScript может скрыть кнопку или не показать форму, но пользователь легко обойдёт это ограничение, отправив запрос вручную.

Нужно ли проверять capability, если nonce уже создан для конкретного пользователя?

Да. Nonce связан с пользователем и контекстом, но не является разрешением на операцию. У авторизованного пользователя может быть nonce, но при этом не быть права изменять конкретную запись или настройку.

Вывод

Nonce и capabilities решают разные задачи: nonce помогает подтвердить легитимность контекста запроса, а capability определяет, имеет ли пользователь право выполнять действие. Надёжный код не подменяет одно другим.

Для каждой операции проверяйте nonce, права доступа и сами данные на сервере. Такой подход защищает не только форму или AJAX-запрос, но и фактическую операцию изменения данных в WordPress.

← Все материалы WordPress