Защита wordpress-формы комментариев от спама без плагинов и капчи

Дневник разработчика

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

Одному моему товарищу знатно наливали спамных комментариев в WordPress, а ставить какие то капчи не хотелось, можно было бы конечно поискать поставить плагин без капчи …  но я честно говоря не особо люблю плагины, т.к. нет ничего лучше собственно написаного или отревьювеного кода (если конечно понимать что делать),  кроме того я уже делал нечто подобное защищая от спама Contact Form 7 без капчи, и подумав что делов тут — «херак, херак и в продакшн» сел за дело.

Однако проблема оказалась сложнее чем я думал, однако в результате все получилось — поехали.

Небольшое лирическое отступление

Первым делом я придумал решить проблему в лоб — просто добавить обязательно скрытое поле, которое бы заполнялось через Javascript. Бот зашел, заполнил поля — нет заполненного поля и форма не отправляется, логично же :).

Быстро загуглив хук модификации формы и накидав код в духе:

add_filter( 'comment_form_default_fields', 'add_antispam_field_to_comment_form' ); //хук
add_filter( 'comment_form_fields', 'add_antispam_field_to_comment_form' ); //хук
function add_antispam_field_to_comment_form($fields) { 
    $fields['csrf'] = ' //Создаем новый элемент массива полей формы
	<input type="text" name="csrf" required style="display:none">  //Собственно это он, делаем его обязательным и не показываем
	<script>document.getElementsByName("csrf")[0].value="antispam"</script> //А тут заполняем его через JS
	';
    return $fields; //Возвращаем массив всех полей назад
}

я был весьма удивлен, что ничего не изменилось, да и сама форма отправлялась как ни в чем не бывало, как будто поле начисто игнорировалось.

Посидев еще минут 30 я нашел причину, оказывается форма рендерилась так

<form action="http://reset.name/wp-comments-post.php" method="post" id="commentform" class="comment-form" novalidate>
...
</form>

видите в конце формы волшебное слово — novalidate, оно как бы намекает, что форма при отправке не будет проверять себя на наличие обязательных полей.

Окей, подумал я, давайте выпилим. Однако открыв тему, оказалось что там всё ок, и этого атрибута у формы то и нету. Снова заюзав поисковик 20 минут спустя я узнал, что WordPress автоматом дописывает это, если в теме указана поддержка html5. Выпиливать это из темы не хотелось, поэтому прошло еще минут 40, пока я нашел рабочее решение через functions.php

add_action( 'after_setup_theme', 'remove_form_novalidate', 11 ); 

function remove_form_novalidate() {
    // This will remove support for post thumbnails on ALL Post Types
    remove_theme_support( 'html5' );
}

Теперь все отлично — novalidate пропал, можно открывать шампанское … однако открыв сайт на следующее утром я обнаружил штук 200 коментов, и полез смотреть логи.

Оказывается что спаммашины не горят желаем эмулировать браузер и работают немного по другому — а именно

  1. Заходят на страницу, чтоб получить идентификатор записи — post_id
  2. Шлют запрос содержащий все необходимые даные напрямую на «приемник» комментариев.

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

Финальный вариант защиты Worpress от спама без капчи и плагинов

  1. Прописываем в код шаблона временную метку, когда страница была запрошена
  2. При отправке формы — обработчик проверяет, сколько времени пользователь провел на странице до отправки комментария, если менее Х секунд. то это спамер (в моем случае пауза стоит 30 секунд, согласитесь 0 если пользователь что-то читал на странице- то он врядли за минуту всё прочитал, да и еще коммент успел оформить, на кол 503 ошибку его), если поле пришло пустое — то это тоже спамер.

Вот что получилось в итоге

add_filter( 'comment_form_default_fields', 'add_antispam_field_to_comment_form' ); 
add_filter( 'comment_form_fields', 'add_antispam_field_to_comment_form' ); 
function add_antispam_field_to_comment_form($fields) { //Эта функция добавляем поле с именем csrf в форму
    $fields['csrf'] = '
	<input type="text" name="csrf" required style="display:none"> //добавляем само поле
	<script>document.getElementsByName("csrf")[0].value="'.time().'"</script> //заполняем его текущей меткой времени через Javascript
	';
    return $fields;
}

add_action( 'pre_comment_on_post', 'action_check_hidden_field' );
function action_check_hidden_field( $comment_post_ID ){ //Функция проверяет прошло ли Х секунд с между моментом отрисовки страницы и отправкой комментария.
	$human_pause = 30; //это и есть Х., т.е. в данном случае Х = 30 секунд
	if (!isset($_POST['csrf']) || ((time() - intval($_POST['csrf'])) < $human_pause) || !preg_match('/\d{10}/',$_POST['csrf'])) { //Если поле пришло пустое, коммент запостили раньше чем через Х секунд, или в коменте пришла какая то ерунда, а не метка времени ... 
		wp_die('Service Unavailable','Service Unavailable', 503); // .. то покажем ошибку и коммент не запостим
	}
}

//Опционально, можно не вставлять.
function remove_form_novalidate() {
    remove_theme_support( 'html5' ); //уберем поддержку html5 в нашей теме, чтоб снизить нагрузку на хостинг, в случае если спам-агрегат работает через свой браузер - он не даст ему отправить комментарий и он даже не дойдет до обработчика
}

полученный код нужно вставить в functions.php вашей темы, Enjoy!

Код по максмуму откоменчен, так что что за что отвечает — думаю понятно. 

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

Конечно это не идеальный способ защиты комментариев WordPress от спама без капчи и обойти его несложно, однако спам проходит методом «ковровых бомбардировок» и заморачиваться с одним каким то конкретным сайтом врядли будут. Если спам в комментарии все равно идет — вам не повезло и вас заказали ))), либо неверно вставлен код

Знаете еще какие то несложные способы? Пишите в комментариях, только не ранее чем через 30секунд после прочтения поста 🙂

Материалы позаимствованы отсюда. тут же сайт автора. Спасибо за чтение.

Юрий Ронин