☰
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. Саме вона повинна визначати, чи дозволено поточному користувачу виконувати операцію. Не залишайте 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