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

Владислав Белецкий
Владислав Белецкий .
Категория:
Комментариев: 0

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

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

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

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

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

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

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

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

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

Если же мы говорим о написании своего собственного плагина, то вся эта история с одноразовыми числами приобретает ещё большую актуальность, потому что вдруг ваш плагин вносит изменения в базу данных 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' ] );
}

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

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(). Пример:

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 и тогда одноразовое число и перестанет действовать.

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

Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии