Антиспам методом игры в «напёрстки»

в 1:46, , рубрики: php, антиспам, Формы ввода, форумы, метки: , ,

Зашёл намедни на свой форум. Обнаружил под две сотни спамеров. Ужаснулся. Задумался. Почитал кучу материалов по способам защиты phpBB, в том числе и здесь. Не нашёл для себя подходящего. Изобрёл свой способ защиты форм от спам-ботов. Сразу говорю, на уникальность не претендует, ибо возможно уже существует, только я об этом не знаю. Панацеей также не является.

Большая часть форм защищается от повторной отправки и примитивных автоматических запросов так называемыми токенами. Суть токена в том, что при формировании формы в сессию записывается некая переменная, содержащая строку случайных символов. При отправке формы проверяется скрытое поле формы, содержащее строку, записанную в сессию. По сути, пока мы не получим HTML-код формы, мы не узнаем этот самый токен. Это защищает скрипт, обрабатывающий форму от автоматического запроса. А при обработке формы переменная токена сбрасывается или меняется. Таким образом при повторной отправке формы токен будет уже недействителен.

Современные спам-утилиты давно уже работают на принципе парсинга HTML-кода форм. Запрашивается форма, парсится её HTML-код, выдёргиваются все токены и т.п… В общем токен нынче может защитить разве что от глюков самих форм. А настроить спам-утилиту под конкретный движок, например phpBB — не проблема. Достаточно указать, какие поля и чем именно нужно заполнять. Да, не спорю, можно накрутить дополнительных полей в форму. Это немного собъёт с толку спамеров. Но ненадолго. Пройдёт некоторое время и спамеры перенастроят свои утилиты конкретно под Ваш форум и с ещё большим остервенением будут гадить.

При помощи механизма сессий можно заставить спамеров попотеть. На принципе токена можно создать поле у которого меняется не значение, а… имя. Притом лучше всего вместо дополнительного поля поменять таким образом стандартное поле, скажем, «имя пользователя». Итак, для начала вводим переменную сессии. Назовём её username_field_id. В этой переменной мы будем хранить ID поля «имя пользователя». А попутно оставим прежний INPUT в качестве поля-ловушки и скроем его с глаз долой. Полагаю, принцип ловушки-пустышки описывать не требуется.

В форме:

<?php
// После того, как стартовала сессия и сделаны все необходимые дела, генерируем рандомную строку...
  $nfi = '';
  for ($c = 0; $c < 16; $c++) {
    $i = rand(0,61);
    $nfi.= substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',$i,1);
  }
// ... и записываем её в сессию  
  $_SESSION['username_field_id'] = 'field'.$nfi;
?>
<input id="username" name="username" type="text" value="" />
<input id="<?php echo $_SESSION['name_field_id']; ?>" name="<?php echo $_SESSION['name_field_id']; ?>" type="text" value="" />

В обработчике:

// После того, как стартовала сессия и сделаны все необходимые дела...
// ... проверяем ловушку-пустышку...
if ($_POST['username'] != '') die('Get the fuck away from my forum, spammer!');
// ... и наше ниндзя-поле
$nfi = $_SESSION['username_field_id'];
if (isset($_POST[$nfi])) {
  if ($_POST[$nfi] != '') {
    // Делаем нужные манипуляции с полем-м-м.
  } else {
    // Сообщаем юзеру, что поле имя не заполнено
  }
} else { die('Get the fuck away from my forum, spammer!'); }

Важно при этом понимать, что атрибут VALUE должен быть принудительно установлен в пустую строку. Иначе есть риск проколоться.

Однако, более продвинутые спам-утилиты могут определить, что наше ниндзя-поле должно содержать, да ещё и ловушку-пустышку не заполнить. Нет таких утилит? Ничего страшного, подумаем на пару шагов вперёд. Поиграем со спамером в «напёрстки» (кто не знает, что это, гуглим сами). Мы создадим по такому же принципу пару дополнительных ловушек-пустышек. Но не просто создадим их, но и перемешаем в рандомном порядке вместе с настоящим полем.

В форме:

<?php
// Упакуем алгоритм генерации в функцию для удобства
function gen_random_string(){
  $nfi = '';
  for ($c = 0; $c < 16; $c++) {
    $i = rand(0,61);
    $nfi.= substr('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',$i,1);
  }
  return $nfi;
}
// После того, как стартовала сессия и сделаны все необходимые дела, генерируем три рандомных поля
  $_SESSION['unf_1'] = 'field'.gen_random_string();
  $_SESSION['unf_2'] = 'field'.gen_random_string();
  $_SESSION['unf_3'] = 'field'.gen_random_string();
// Генерируем, какое из этих полей будет настоящим
  $_SESSION['unf_t'] = rand(1,3);
?>
<input id="username" name="username" type="text" value="" />
<?php
  for ($c = 1; $c < 4; $c++) {
    echo '<input id="'.$_SESSION['unf_'.$c].'" name="'.$_SESSION['unf_'.$c].'" type="text" value="" />';
  }
?>

В обработчике:

// После того, как стартовала сессия и сделаны все необходимые дела получаем номер настоящего поля
$true_field = $_SESSION['unf_t'];
// ... проверяем ловушки-пустышки...
if (isset($_POST['username'])) if ($_POST['username'] != '') die('Get the fuck away from my forum, spammer!');
for($c = 1; $c < 4; $c++) if ($c != $true_field) {
  $fn = $_SESSION['unf_'.$c];
  if (isset($_POST[$fn])) if ($_POST[$fn] != '') die('Get the fuck away from my forum, spammer!');
}
// ... и наше ниндзя-поле
$nfi = $_SESSION['unf_'.$true_field];
if (isset($_POST[$nfi])) {
  if ($_POST[$nfi] != '') {
    // Делаем нужные манипуляции с полем-м-м.
  } else {
    // Сообщаем юзеру, что поле имя не заполнено
  }
} else { die('Get the fuck away from my forum, spammer!'); }

При этом нам нужно скрыть поля пустышки, оставив только настоящее поле. Делать будем посредством внешней таблицы стилей. Подключать к основному стилю её мы будем через директиву import.

В основном стиле:

@import 'generator.php';

PHP-генератор таблицы стилей generator.php:

<?php
// Устанавливаем нужный заголовок
  header("Content-type: text/css;");
// Всеми правдами-неправдами запрещаем кэширование
  header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); //Дата в прошлом 
  header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1 
  header("Pragma: no-cache"); // HTTP/1.1 
  header("Last-Modified: ".gmdate("D, d M Y H:i:s")."GMT");  
// Получаем истинное поле
  session_start();
  $n = $_SESSION['unf_t'];
// Скрываем все поля, кроме истинного
  for($c = 1; $c < 4; $c++) if ($n != $c) echo '#'.$_SESSION['unf_'.$c].' { display: none; }';  
?>

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

Изначально данное решение писалось в расчёте на конкретный движок — phpBB. Однако, разобраться, как интегрировать данное решение в движок, я пока не смог. Как разберусь, напишу ещё.

Автор: XanderBass


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js