Внутри ядра WordPress «скрыто» множество полезных инструментов. Один из них — это токены безопасности или «nonces», которые позволяют защититься от некоторых видов неправомерного или злоумышленного использования возможностей сайта.
Их не видно в интерфейсе страниц, но они широко используются в административной части WordPress. В рамках данной статьи будут рассмотрены примеры использования, логика создания и работы с nonces, которую возможно полностью заменить своей собственной системой.
Nonce. Number Used Once
Nonce расшифровывается как «число, используемое один раз» (number used once). Эти числа или «токены» состоят из цифр и букв, являются уникальными для каждого пользователя и для каждого действия, и имеют ограниченное «время жизни», после которого становятся недействительными.
Несмотря на говорящую фразу «число, используемое один раз», в действительности это не совсем так. Nonce генерируется для конкретного пользователя и действия, поэтому будет неизменной до тех пор, пока жизненный цикл не будет закончен (примеры генерации и описание жизненного цикла будут рассмотрены ниже).
Одноразовыми же они называются, потому что предназначены для тех же целей, что и настоящие одноразовые токены. Например, nonce помогают защититься от некоторых типов атак, включая межсайтовую подделку запросов (CSRF), но не защищают от атак повторного воспроизведения (replay atack), потому что nonce не проверяются на использование лишь единожды.
В качестве примера того, как nonce используется, рассмотрим некую административную страницу, на которой отображается ссылка для удаления записи с идентификатором 123. Можно заметить, что nonce содержится в конце адреса.
https://wpmag.ru/wp-admin/post.php?post=123&action=trash&_wpnonce=b192fc4204
Если кто-нибудь попытается изменить адрес, чтобы удалить запись с идентификатором 456, то число nonce будет недействительным и попытка удалить другую запись не увенчается успехом.
https://wpmag.ru/wp-admin/post.php?post=456&action=trash&_wpnonce=b192fc4204
Неверное число nonce заставит WordPress выдать в ответ браузеру «403 Forbidden» с сообщением об ошибке «Вы уверены, что хотите сделать это?».
Создание чисел nonce
В WordPress уже заранее есть необходимый функционал для создания и добавления nonce в качестве аргумента URL или скрытого поля внутри формы. Есть несколько базовых функций и для работы в каких-то иных ситуациях.
Nonce, используемые в AJAX-запросах, обычно добавляются в скрытые поля формы, откуда к ним может получить доступ JavaScript. Стоит заметить, что nonce уникальна для текущей сессии пользователя, поэтому, если входить или выходить асинхронно, nonce на странице станут недействительными.
Генерация nonce
Процесс генерации происходит c использованием ключа и соли, уникальных для каждого сайта, если конечно WordPress установлен корректно. Для этого используются константы NONCE_KEY
и NONCE_SALT
, объявляемые в файле wp-config.php
.
Иногда в качестве значений установлена строчка «впишите сюда уникальную фразу». В этом случае необходимо воспользоваться API для создания секретных ключей, заменив подобные значения по умолчанию. После этой процедуры все пароли пользователей останутся действительными, но им прийдется снова выполнить вход.
Где хранятся nonce
Система генерации чисел nonce не сохраняет их в базе данных, как может показаться на первый взгляд. В момент проверки токен генерируется заново и сравнивается с тем, который был получен.
Nonce в качестве аргумента URL
Для того, чтобы добавить nonce к URL существует функция wp_nonce_url()
, которой необходимо передать URL и строчку, обозначающую некое действие. Например:
$complete_url = wp_nonce_url( $bare_url, 'trash-post_' . $post->ID );
Для максимальной безопасности, убедитесь, что строка, описывающая действие, названа максимально конкретно. В примере выше название действия будет выглядеть, как trash-post_123
.
По умолчанию, wp_nonce_url()
добавляет в адрес аргумент с именем _wpnonce
. Но можно указать и другое название при вызове функции, передав третий аргумент:
$complete_url = wp_nonce_url( $bare_url, 'trash-post_' . $post->ID, 'wpmag_nonce' ); // Теперь аргумент будет называться wpmag_nonce
Числа nonce внутри формы
Для добавления nonce внутрь формы используется функция wp_nonce_field()
. В качестве аргумента, как и в случае с добавлением nonce в адрес, необходимо передать строку, которая описывает совершаемое действие.
По умолчанию, функция выведет два скрытых поля. Первое содержит секретное число nonce. Внутри второго поля — текущий URL (referrer). Например:
wp_nonce_field( 'delete-comment_' . $comment_id );
При таком вызове в форму выведутся следующие скрытые поля:
<input type="hidden" id="_wpnonce" name="_wpnonce" value="796c7766b1" /> <input type="hidden" name="_wp_http_referer" value="/wp-admin/edit-comments.php" />
Как и в предыдущем примере, для максимальной безопасности строчку, описывающую действие, следует задавать максимально конкретно.
Кроме этого можно задать другое имя для поля nonce, не выводить поле с referrer и указать, что результат необходимо вернуть, а не выводить. Для подробностей использования смотрите страницу описания функции wp_nonce_field() в кодексе WordPress.
Нестандартное использование nonce
Для создания nonce при использовании в других случаях используйте функцию wp_create_nonce()
, передав в качестве аргумента строчку, описывающую действие. Например:
$nonce = wp_create_nonce( 'my-action_' . $post->ID );
После этого, в переменной $nonce
окажется уникальный ключ, например, 295a686963
.
Не забывайте, что и здесь для максимальной безопасности в качестве аргумента необходимо передавать строчку, как можно более точно описывающую совершаемое действие.
Проверка действительности чисел nonce
Для всех трех способов внедрения nonce, описанных в предыдущей части, есть функции, с помощью которых можно выполнить проверку действительность токена.
Проверка nonce, полученной с административной страницы
Для проверки nonce, переданной через URL или внутри формы в административной панели WordPress, необходимо использовать функцию check_admin_referer()
, передав в качестве аргумента строчку, описывающую название действия.
check_admin_referer( 'delete-comment_' . $comment_id );
Функция проверит поля nonce и referrer и если проверка не будет успешной, то произойдет обычное действие — прекращение выполнения кода с ответом 403 и сообщением об ошибке.
Если вы использовали название поля для nonce отличное от того, что задается по умолчанию, необходимо указать его вторым аргументом:
check_admin_referer( 'delete-comment_' . $comment_id, 'wpmag_nonce' );
Учтите, что данная функция работает только в рамках административной панели WordPress, и не подойдет для использования в формах, публикуемых на лицевой части сайта. Если требуется проверить действительность кодов nonce на лицевой части, используйте функцию wp_verify_nonce()
(см. ниже).
Проверка nonce, полученной через AJAX-запрос
Если вы получили nonce через AJAX-запрос, используйте функцию check_ajax_referer()
. В качестве аргумента необходимо передать название действия:
check_ajax_referer( 'process-comment_' . $comment_id );
Функция проверит nonce и в случае, если проверка не будет успешной, завершит исполнение кода. Учтите, что при таком подходе поле referrer не учитывается, но данная функция убедится в том, что запрос действительно пришел по AJAX.
Если вы используете название поля для nonce, отличное от того, что задается по умолчанию (_wpnonce
или _ajax_nonce
), необходимо передать дополнительные параметры. Подробности на странице функции check_ajax_referer()
в кодексе WordPress.
Проверка nonce, полученной другими способами
Nonces, полученные другими способами, в том числе и на лицевой части сайта, могут пройти проверку с помощью функции wp_verify_nonce()
. Данная функция принимает два аргумента: значение полученного кода nonce и название действия. Например:
wp_verify_nonce( $_REQUEST['my_nonce'], 'process-comment' . $comment_id );
В случае отрицательного результата не следует продолжать обработку запроса. Обычно, в таких случаях вызывается wp_nonce_ays()
, которая отдает браузеру «403 Forbidden» с сообщением об ошибке.
Изменение системы nonce в WordPress
Систему работы с nonce можно изменять, используя различные события и фильтры. Некоторые функции для работы с nonce размещены в файле pluggable.php
, и это означает, что они могут быть легко заменены своими собственными.
Например, для изменения логики проверки АJAX-запросов, необходимо написать свою собственную функцию check_ajax_referrer()
в плагине. Список функций и фильтров для работы с nonce можно найти в кодексе.
Изменение продолжительности жизни кодов nonce
По умолчанию, срок действия nonce равен одному дню. После этого число nonce перестает быть действительной, даже если название действия совпадает. Для изменения времени используйте фильтр nonce_life
, указав необходимое время в секундах. Например, код ниже устанавливает срок действия на 4 часа:
add_filter( 'nonce_life', function() { return 4 * HOUR_IN_SECONDS; } );
Обратите внимание, что nonce, как говорилось выше, это не «номер, используемый единожды». Срок действия nonce не совпадает со сроком действия настоящих одноразовых токенов.
Для отсчета срока жизни nonce, WordPress использует систему с двумя «тиками» часов, где 1 тик равносилен половине продолжительности жизни. Во время второго тика nonce может быть обновлена, например, через автосохранение.
При настройках по умолчанию, где продолжительность действия nonce составляет 24 часа, это означает, что информация о времени внутри nonce, говорит сколько двенадцатичасовых периодов времени прошло с начала эпохи Unix.
Nonce, созданная в промежуток с 12:00 до 24:00 будет действительна до 12:00 следующего дня. Фактический срок службы составит значение между 12 и 24 часами. Пример выше, где срок службы устанавливался на 4 часа, означает, что nonce будет действительна от 2 до 4 часов.
Чтобы до конца понять суть взглянем на исходный код функции wp_nonce_tick()
, которая возвращает количество 12-и часовых промежутков.
function wp_nonce_tick() { $nonce_life = apply_filters( 'nonce_life', DAY_IN_SECONDS ); return ceil(time() / ( $nonce_life / 2 )); // Поделив текущее время на 12 часов мы получаем кол-во // 12 часовых промежутков, которые уже прошли :) }
А теперь на функцию wp_verify_nonce()
, проверяющую nonce и заодно отсчитывающую количество тиков.
function wp_verify_nonce( $nonce, $action = -1 ) { // ... if ( empty( $nonce ) ) return false; $i = wp_nonce_tick(); // Nonce созданные до 12 часов назад $expected = substr( wp_hash( $i . '|' . $action . '|' . $uid . '|' . $token, 'nonce'), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) return 1; // Идет первый тик // Nonce созданные в промежуток от 12 до 24 часов назад $expected = substr( wp_hash( ( $i - 1 ) . '|' . $action . '|' . $uid . '|' . $token, 'nonce' ), -12, 10 ); if ( hash_equals( $expected, $nonce ) ) return 2; // Идет второй тик return false; // Неверная nonce }
Выполнение дополнительных проверок
Для выполнения дополнительных проверок в момент, когда функция check_admin_referer()
установила, что nonce и referrer являются действительными, можно использовать check_admin_referer
в качестве события:
function wpmag_additional_check( $action, $result ) { // Проверяем } add_action( 'check_admin_referrer', 'wpmag_additional_check', 10, 2 );
Аналогичным способом можно «прицепиться» к событию check_ajax_referer
внутри вызова check_ajax_referer()
.
Изменение внешнего вида страницы с ошибкой
Внешний вид страницы с сообщением об ошибке, отдаваемой в случае недействительности nonce, можно изменить. Функция wp_nonce_ays()
передает необходимое сообщение об ошибке функции с драматичным названием wp_die()
. В последней есть несколько фильтров для раздельной обработки AJAX, XML-RPC и всех остальных запросов. Например:
$function = apply_filters( 'wp_die_handler', '_default_wp_die_handler' ); // В $function записывается название функции, которую вызовет wp_die()
Перехватив фильтр, можно указать собственную функцию, которая сможет отдавать страницу с совершенно другим внешним видом.
В заключение следует отметить, что если вы разрабатываете темы и особенно плагины для WordPress, не забывайте использовать одноразовые числа nonce во всех интерфейсах, поскольку их отсутствие может легко привести к взлому сайтов ваших пользователей.
Если у вас остались вопросы по системе токенов в WordPress, оставьте комментарий и мы обязательно вам ответим.