Избавление от KAPTCHA, раз и на всегда

Фразы «введите символы с картинки» начали всех уже не просто доставать, а их не читабельность, ну просто выводит из себя. Примерно с полгода назад я задумался а как можно обойтись без нее и все оказалось гораздо проще чем можно представить. Ну для начала посмотрим в историю, от куда появились «каптчи» и зачем — ответ очевиден, для того, что-бы избавиться от роботов разного уровня. Исходя из данного можно подумать теперь над другим: ботов пишут для сайтов типа форумы, блоги, т.е. тех куда можно в свободной и бесплатной форме запостить рекламное сообщение. Посмотрим на это со стороны «бото-писца», как пишут ботов?:

  1. Анализируют сайт
  2. Смотрят все формы и методы, куда и какие поля передаются
  3. Формируют агрегатор с использованием курлов либо сокетов
  4. Тестируют агрегатор на предмет отлавливания каких либо дополнительных полей
  5. Уже после этого запускают, наслаждаясь жизни


Можно понять, что сгенеренные поля или еще какие уловки хороший бот может отловить сразу, но все думают что каптчу то не сможет. В принципе «каптчу» отловить тяжело, для этого нужен либо алгоритм хеширования символов, но как правило используется не обратимый — такой метод не подходит, читать саму картинку — знавал человека который сделал сканер картинок, но не всем такое дано, использовать сервисы которые предлагают распознавать картинки — идеально. Вывод напрашивается сам: «каптча» не панацея, кому нужно, тот и ее пройдет, тогда зачем нам, разработчикам напрягать пользователей? Ну без защиты боты регаются и постятся в не малых количествах… Вот тут и появилась задача, для решения данной проблемы. Итак мы знаем что боты не умеют:
  1. Выполнять JS
  2. Формировать XmlHTTPRequest
  3. Читать базу — доступ до нее ни кто не даст

Исходя из такого громадного наличия инструментов, можно сделать свой алгоритм доступа. Не претендую стать первым и самым умным в данном вопросе, но пока что, данной технологии не встречал ни где, кроме своих сайтов и сайтов моих клиентов, разумеется. Начнем создавать «антибот» систему, в примере буду приводить выдержки кода с применением ZF и Doctrine

Итак, для начала мы создадим таблицу для временных записей, обзовем ее скажем так:
Antibot (
  id - bigint(20)
  unic_id - varchar(10)
  hash - varchar(50)
  mask - varchar(10))
  time_cr - bigint(20)
  ip - varchar(20)
);


так же создадим файлик для AJAXa — fish.phtml, с содержанием
(«разумееться с проверкой в контроллере if ($this->_request->isXmlHttpRequest()){»):
$tmp_data = Doctrine_Query::create()
    ->select('*')
    ->from('Antibot')
    ->where('unic_id = ?', $this->data)
    ->execute()
    ->toArray();

// эти данные нам нужны будут для вывода и замены данных в форме, об этом ниже
echo $tmp_data[0]['mask']."|||".$tmp_data[0]['hash']."|||gogo";


Вернемся к форме регистрации или свободного поста, неважно к какой форме… но она должна иметь вот такую вставку:
// ориентир, время, метка
$crast = dechex(time()+rand(00,99));
 
// Функция рендома символов
   function random_simbols() {
/* тут может быть любой агрегатор рендомайзинга символов, зависит от вашей фантазии */
   }
 
/* получаем ip посетителя, таким методом мы получим ip в любом случае и в любом это будет именно ip без проксей */
   if (! function_exists('getenv')) {
      $client_ip = $HTTP_SERVER_VARS['HTTP_CLIENT_IP'];
      $x_frd_for = $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'];
      $rmte_addr = $HTTP_SERVER_VARS['REMOTE_ADDR'];
   } else {
      $client_ip = getenv('HTTP_CLIENT_IP');
      $x_frd_for = getenv('HTTP_X_FORWARDED_FOR');
      $rmte_addr = getenv('REMOTE_ADDR');
   }
   if ($client_ip) {
      $x_cip = explode(".", $client_ip);
      $x_rmt = explode(".", $rmte_addr);
      $ip = ($x_cip['0'] != $x_rmt['0']) ? implode(".", array_reverse($x_cip)) : $client_ip;
   } elseif ($x_frd_for) {
      $ip = (strstr($x_frd_for, ",")) ? end(explode(",", $x_frd_for)) : $x_frd_for;
   } else {
      $ip = $rmte_addr;
   }
 
   $mask = explode(".", $ip);
 
// создадим херню полную - извините за выражения, но оно так и есть
   $unic_id = $mask[0] * ($mask[1] + $mask[2] + $mask[3] + rand(000,999));
 
// создадим хеш для сравнения, потом пригодиться 
   $hash = md5($unic_id."".random_simbols()."".$crast);
 
// создаем временную запись, в нашей антибот таблице
   $thmdata = new Antibot();
   $thmdata->unic_id = $unic_id;
   $thmdata->hash = $hash;
   $thmdata->mask = $crast;
   $thmdata->time_cr = time();
   $thmdata->ip = $ip;
   $thmdata->save();


ну и перейдя в саму форму добавим вот такое вот поле:
<input type="hidden" value="<?=$unic_id?>" id="crast" />


В принципе все, остался только AJAX обработчик, напишем и его:
<script>
$(document).ready(function(){
 
// Повешаем событие на обработку кнопки субмита
   $('#submit').click(function(){
      doitnow='stop';
      $.ajax({
         type:"GET",
 
   // Обратимся к нашему файлику, который создали вначале
         url:"/index/fish/",
         data:"data="+$('#crast').val(),
 
   // Очень важная штука, без нее запрос будет выполняться не последовательно!
         async:false,
 
   // Если запрос прошел хорошо, т.е. AJAX выполнился действуем по нашим правилам
         success:function(msg){
 
    // Вот это, мы как раз и получаем из "фиша" и из полученных данных составляем свои
            msg=msg.split("|||");
            doitnow=msg[2];
 
   // Помните, выше мы добавили в форму инпут, вот мы меняем ему имя и значения на лету, для минимилизации перехвата
            $('#crast').attr({name:msg[0],value:msg[1]});
 
   // Добавим к акшену формы дополнительный параметр
            $("form").attr('action',$("form").attr('action')+'/?crast='+$('#crast').attr('name'));
 
   // Закончили изваращаться над формой и элемента, пора в путь, жмем субмит
            $("form").submit(function(){
               if(doitnow=='gogo'){
                  return true;
               }return false;
            });
         },error:function(){
            alert('Произошла ошибка, нажмите "Вход в систему" еще раз');
         return false;
      }});
   });
});
</script>


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

unic_id = 44958
hash = 03cf63bea12bfcd3644846baa8481d07
mask = 4ca388cf
time_cr = 1285785784
ip = ххх.ххх.ххх.ххх

Далее мы записываем инпут с содержанием лишь одного ориентира:
<input type="hidden" id="crast" value="44958">


После нажатия на кнопку «субмит» и обработки JS в форму были добавлены следующие параметры:
form action="/register/?crast=44958"

<input type="hidden" id="crast" name="4ca388cf" value="03cf63bea12bfcd3644846baa8481d07">


ну и соответственно это все послыается постом на адрес xxx/register/crast=44958, что же происходит в обработчике поста, рассмотрим:
// Если данный парам CRAST пустой значит пост искуственный
   if ($_GET['crast'] == '') {
      $error = "Вы наверное бот :-)";
 
        // удалим нашу временную запись, что бы потом не смогли к ней обратиться
                $tmp_data = Doctrine_Query::create()
                 ->delete()
                 ->from('Antibot')
                 ->where('mask = ?', $_GET['crast'])
                 ->execute();
   } else {
// Возьмем данные из таблицы для сравнения
      $tmp_data = Doctrine_Query::create()
            ->from('Antibot')
            ->where('mask = ?', $_GET['crast'])
            ->execute()
            ->toArray();
   }
 
// Сравниваем полученные данные из таблицы и из поста, на их соответствие, вдруг их нагенерили свим способом, такое тоже возможно
if ($this->_request->getPost ($_GET['crast']) != $tmp_data[0]['hash']) { 
    // отловилась ошибка это робот
    $error = "yes";
// удалим нашу временную запись, что бы потом не смогли к ней обратиться
                $tmp_data = Doctrine_Query::create()
                 ->delete()
                 ->from('Antibot')
                 ->where('mask = ?', $_GET['crast'])
                 ->execute();
} else {
   // скорее всего это человеческий пост
    $error = "no";
// удалим нашу временную запись, что бы потом не смогли к ней обратиться
                $tmp_data = Doctrine_Query::create()
                 ->delete()
                 ->from('Antibot')
                 ->where('mask = ?', $_GET['crast'])
                 ->execute();
}


В общем все, теперь смотрим у себя в проверках и выполняем нужные нам функции для регистрации, постинга и т.д.

Итоги: мы получили «защитника» от ботов, простым без «капчевым» путем.
Предугадываю некоторые нападения разъясню:
«Выполнять JS» — да это так, JS можно с эмулировать, но не факт что эмулированный JS выдаст нужные результаты
«Читать базу» — в нашем примере, JS служит лишь для замены и вызова параметров из таблицы, а они у нас генеряться фоном при загрузке, а т.к. их нет не на странице ни в заголовках, то их определить очень проблематично. То что вставка происходит на лету, этого бот отловить не сможет, а если и сможет, то тому мастеру который это сделает, ну просто не нужен будет Ваш форму или блог или еще что по проще, ему по зубам будет система безопасности банк-клиентов :-)


0 комментариев

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.