Одноразовые числа (Nonces)

Эта тема уже затрагивалась на некоторых сайтах, тем не менее хочу описать её ещё и по-своему.

Вполне возможно, что иногда в коде вы могли сталкиваться с функциями типо wp_verify_nonce() или wp_nonce_field() или что-то-там...nonce(). Эти функции нужны для генерации и проверки одноразовых чисел. Они так называются, потому что nonce – это сокращение «Number used ONCE». Но кстати, несмотря на название, они не проверяются на воспроизведение лишь одиножды.

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

Для чего они нужны?

Легче всего это рассмотреть на примере админки WordPress. Если мы перейдём на страницу с постами, и проинспектируем ссылку «Удалить запись», то найдём там URL.

То есть, взглянув на эту ссылку, можно легко понять, что прямая ссылка удаления любого поста в WordPress имеет вид адрес-сайта/wp-admin/post.php?post={ID поста}&action=trash. Получается, что нам достаточно подставить любой адрес любого сайта и какой-то произвольный ID поста и он будет удалён??

Конечно нет. А почему?

По двум причинам:

  1. Во-первых, конечно же в WordPress будет произведена проверка прав пользователя, может ли он в принципе удалять эту запись.
  2. Во-вторых, видите параметр _wpnonce=057c817593? Этот параметр как раз и содержит одноразовое число – случайно сгенированную последовательность, индивидуальную для каждого пользователя, которая будет действительна в течение 24 часов. WordPress попытается проверить и её тоже, чтобы убедиться в подлинности перехода по ссылке удаления поста.

Если проверка подлинности запроса не пройдёт, то получим такое сообщение:

проверка подлинности запроса не удалась

Если же мы говорим о написании своего собственного плагина, то вся эта история с одноразовыми числами приобретает ещё большую актуальность, потому что вдруг ваш плагин вносит изменения в базу данных WordPress или же имеет какую-то уязвимость, которую злоумышленниик смогут легко использовать, скачав где-либо и изучив код вашего плагина.

Ещё один важнейший момент при написании плагина для WordPress – это очистка и валидация данных, об этом у меня на сайте есть видеоурок.

Давайте рассмотрим пример из реальной жизни.

Предположим кто-либо создаёт произвольный профиль пользователя в виде шаблона страницы. И там находится HTML-формы с полями типа Имя, Адрес, Пароль и т.д.

Типо так:

echo '<form action="update.php" method="POST">
<input type="text" name="name" value="" />
<input type="password" name="pass" value="" />
<input type="hidden" name="user_id" value="' . get_current_user_id() . '" />
</form>';

А потом обработчик формы update.php содержит код типо:

if( ! empty( $_POST[ 'pass' ] ) ) {
	wp_set_password( $_POST[ 'pass' ], $_POST[ 'user_id' ] );
}

В итоге ребятки, изучив код вашего личного кабинета, заходят в свой личный кабинет на вашем сайте, открывают инспектор браузера, меняют ID пользователя на «1», потому что по умолчанию это ID администратора, если только вы его не поменяли. И тем самым сбрасывают пароль! Потому что могут!

Попробуем защитить наш код:

echo '<form action="update.php" method="POST">
<input type="text" name="name" value="" />
<input type="password" name="pass" value="" />
<input type="hidden" name="_wpnonce" value="' . wp_create_nonce( 'true_update' ) . '" />
<input type="hidden" name="user_id" value="' . get_current_user_id() . '" />
</form>';

И вторая часть кода:

if( 
   ! empty( $_POST[ 'pass' ] ) 
   && ! empty( $_POST[ '_wpnonce' ] ) 
   && wp_verify_nonce( $_POST[ '_wpnonce' ], 'true_update' ) 
) {
	wp_set_password( $_POST[ 'pass' ], $_POST[ 'user_id' ] );
}

Уже лучше! Так как одноразовое число будет сгененировано индивидуально для пользователя, то в параметр, где я записал «true_update», ни к чему передавать ID пользователя. Да и ещё его можем перенести из скрытого поля сразу же во вторую часть кода.

Список функций

Ниже в таблице – основные функции для работы с одноразовыми числами.

ФункцияНазначение
wp_create_nonce()Создаёт непосредственно одноразовое число.
wp_verify_nonce()Позволяет проверить одноразовое число.
wp_nonce_field()Создаёт скрытое поле <input type="hidden">, содержащее одноразовое число.
wp_nonce_url()Добавляет одноразовое число в качестве $_GET-параметра в URL.
check_ajax_referer()Проверяет подлинность AJAX-запроса.
check_admin_referer()Проверяет подлинность отправки формы в админке WordPress.

Защита форм

Чуть выше, в примере, я уже попробовал защитить форму при помощи функций wp_create_nonce() и wp_verify_nonce().

Но в форме будет более актуально использовать другую функцию для генерации одноразового числа – wp_nonce_field(). Пример:

wp_nonce_field( 'remove-post-' . $post_id );

Допустим, мы применим код на странице «https://misha.blog/members-area», Получим:

<input type="hidden" id="_wpnonce" name="_wpnonce" value="797c7777b7" />
<input type="hidden" name="_wp_http_referer" value="/members-area" />

Затем, уже непосредственно в обработчике формы, нам нужно проверить эту историю.

if( isset( $_POST[ '_wpnonce' ] ) && wp_verify_nonce( $_POST[ '_wpnonce' ], 'remove-post-' . $post_id ) ) {
	// отлично, проверка прошла
}

Защита URL

Для создания URL с одноразовым числом в нём мы конечно же можем использовать напару две функции wp_create_nonce() и add_query_arg(), но какой в этом смысл, если есть отдельная функция wp_nonce_url()?

$url = wp_nonce_url( 
	add_query_arg( // первый параметр – оригинальный URL
		array(
			'action' => 'remove_post',
			'post'   => $post_id,
		),
		site_url()
	),
	'remove-post-' . $post_id // второй параметр – ключ одноразового числа
);

И проверяем:

if( isset( $_GET[ '_wpnonce' ] ) && wp_verify_nonce( $_GET[ '_wpnonce' ], 'remove-post-' . $_GET[ 'post' ] ) ) {
	// отлично, проверка прошла
}

Защита AJAX-запросов

Об этом у меня на сайте есть отдельный видеоурок, рекомендую его чекнуть.

Изменение времени жизни

По умолчанию срок жизни одноразового числа равен 24 часам. Но мы можем изменить его при помощи хука nonce_life и констант времени WordPress.

add_filter( 'nonce_life', 'true_change_nonce_lifespan' );
 
function true_change_nonce_lifespan() {
 
	return 8 * HOUR_IN_SECONDS; // возвращаем время в секундах
 
}

Тут думаю также важно упомянуть, в качестве одного из компонентов хеша одноразового числа WordPress использует количество 12-и часовых промежутков времени, прошедших с начала эпохи UNIX. А если мы изменяем время жизни nonce при помощи хука, то это уже будут не 12-и часовых промежутки, а то время, которые вы задали, поделённое на два. Например я использовал 8 часов, значит это будет 4.

Предположим, что я не изменял время жизни хуком (осталось 24, как и было) и что сейчас я создал одноразовое число, в 17:00, и количество этих промежутков равно 30000. И получается, моё одноразовое число будет действовать до тех пор, пока количество промежутков не сменится на 30002. А значит завтра до 12:00.

Предположим, что я поменял время жизни хуком. Сейчас так же 17:00, новое время действия nonce 8 часов. Получается первый «тик» начался в 16:00 и закончится в 20:00, а второй – в 0:00 и тогда одноразовое число и перестанет действовать.

В общем сложно, но в целом думаю всё понятно 🙃

Миша

Впервые познакомился с WordPress в 2009 году. Организатор и спикер на конференциях WordCamp. Преподаватель в школе Нетология.

Если вам нужна помощь с сайтом или разработка с нуля на WordPress / WooCommerce — пишите. Я и моя команда будем рады вам помочь!

Оставить комментарий

Если вы хотите добавить код, не забудьте обернуть его в <pre lang="php"></pre>, если же код – меньше одной строчки, то можно и в <code></code>.