Posts Tagged ‘каптча’

Защита сайта от спама при помощи каптчи

Posted in Статьи on Январь 20th, 2009 by admin – Оставьте первый комментарий

Данная статья описывает механизм защиты сайта от спама при помощи каптчи. В статье приведен код PHP, реализующий данную защиту, ориентированный на начинающих программистов.

МЕХАНИЗМ ЗАЩИТЫ САЙТА ОТ СПАМА ПРИ ПОМОЩИ КАПТЧИ

Если в интернете есть сайт, который зарегистрирован в поисковых системах, и если на этом сайте есть страница, на которой посетитель может ввести какие-либо данные, то данный сайт, несомненно, будет подвергаться попыткам спама. Целью данного спама является размещение текста рекламного характера, поэтому, на каждом блоге, где есть возможность оставить комментарии, в каждой гостевой книге, в каждом форуме будут наблюдаться попытки оставить сообщение рекламного характера.

Для защиты от подобного спама и применяются так называемые каптчи. Вообще говоря, каптча - это небольшой графический рисунок, на котором нарисованы (именно нарисованы!) несколько букв и/или цифр и смысл защиты состоит в том, чтобы вынудить посетителя сайта, желающего оставить сообщение, ввести изображенные на рисунке буквы и цифры. Сегодня под термином “каптча” скрывается не только изображение с несколькими буквами, а целый ряд различных изощренных (очень часто излишне изощренных) методов ограничения сообщений посетителей сайта. Это может быть и рисунок (с подписью “что изображено на рисунке?”), это может быть и текстовый вопрос (например, “какой сейчас год?”). Как бы то нибыло, но посетителя сайта просят ответить на вопрос, ответ на который не может дать компьютер или вообще может дать лишь узкий круг людей - целевых посетителей.

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

Это, можно сказать, первый уровень защиты, и он необходим во всех случаях, даже если речь идет о сайте, который посещает всего несколько человек в день. Но если сайт очень популярен и каждый день его странички просматривают тысячи людей, то ценность рекламного сообщения на нем значительно возрастает, поэтому, может быть полезна защита также и от реальных людей, которые оставят сообщение вручную. В этом случае применяется, как правило, текстовая каптча, содержащая вопрос, ответ на который знают только постоянные посетители, например, у желающего высказаться можно спросить об имени автора сайта. Перед тем, как применять данную защиту следует дважды подумать: ведь около 93% (по мнению автора) всех сообщений рассылаются автоматизированными программами, от которых есть защиты попроще и которые позволят посетителям сайта высказать свое мнение без лишней раздражительности.

Еще один метод защиты (я его не проверял, но должен работать) - это изменить названия полей в форме. Робот, осуществляющий постинг спама, ориентируется в первую очередь на названия полей в форме, поэтому можно написать, например, такую форму:

  1. Имя: <input type="text" name="field1"><br>
  2. Сообщение: <input type="text" name="field2"><br>
  3. <input type="text" name="login"><input type="text" name="message">

Последние два поля (login и message) скрываются css или JavaScript. При этом бот будет заполнять именно эти поля, а не field1 и field2 и, получив форму, можно принимать пост только в том случае, если поля login и message пустые.
Лично я считаю, что просто необходимо применять каптчи, позволяющие защититься от ботов (программ) и не рекомендует выдумывать каптчи, которые должны отсеивать всех, кто не из “нашей песочницы”, поэтому далее рассмотрен механизм защиты именно от ботов при помощи обычной графической каптчи.

Принцип работы основан на следующем: сначало генерируется код каптчи, состоящий из нескольких букв и/или цифр, затем создается небольшой рисунок, на котором вырисовываются код каптчи. после этого данный рисунок отсылается клиенту в браузер вместе с формой ввода какого-либо текста (например, сообщения гостевой книги). Пользователь заполняет все поля формы, в том числе и вводит текст, изображенный на рисунке и отсылает данные на сайт. Теперь сервер просто сравнивает известный ему код каптчи (он сохраняется в сессии) с тем, что ввел пользователь и вот уже можно судить о том, человек его ввел или компьютер.

Скрипт простой и эффективной каптчи

Теперь попробуем реализовать это на практике. Для примера возьмем видоизмененный скрипт KCAPTCHA 1.2.6.

Наша каптча будет реализована следующим образом… Основная работа будет проводиться в файле captcha.php. Этот файл генерирует код и создает рисунок. Чтобы не заворачиваться с создаваемыми файлами, в которые будет записываться создаваемый рисунок, скрипт captcha.php будет отсылать данные непосредственно в браузер, поэтому мы и будем вставлять этот рисунок в тег <img>.

Данный скрипт также помещает в сессию код каптчи в переменную CaptchaKey. Второй файл - test.php, будет содержать форму ввода и скрипт проверки. Очень полезным свойством данной каптчи является то, что она не пишет текст на создаваемом рисунке, а просто накладывает на него буквы из специально подготовленного алфавита (для этого созданы рисунки в файле /fonts). Это означает, что для работы каптчи не требуется библиотек работы с шрифтами (Libttf, ImageMagic), которые установлены не у всех хостеров.

Итак, сначала представим форму ввода:

  1. <form action="" method="POST">
  2. <input type="hidden" name="formfill" value=1>
  3. <img src="captcha.php"><br>
  4. Введите изображенный на рисунке текст: <input type="text" name="capt"><br>
  5. <input type="submit">
  6. </form>

Тут все элементарно: скрипт, как уже говорилось, открывает сессию, генерирует код каптчи, сохраняет этот код в сессии, генерирует рисунок и выводит его в браузер, а тег <img> вставляет этот рисунок в html-страницу.

Чтобы сразу закончить с файлом test.php дополним его скриптом проверки. Вот полный код test.php:

  1. <?php
  2. session_start();
  3. if(isset($_POST[‘formfill’])) { //Форма уже была заполнена
  4. if(!$_SESSION[‘CaptchaKey’]) die(‘Проверочный код введен неверно. Возможно, отключены cookies’);
  5. if($_SESSION[‘CaptchaKey’] &amp;&amp; $_POST[‘capt’]!=$_SESSION[‘CaptchaKey’]) die(‘Проверочный код введен неверно!’);
  6. echo ‘Код правильный!!!’;
  7. $_POST[‘capt’]=;
  8. }
  9. ?>
  10. <form action="" method="POST">
  11. <input type="hidden" name="formfill" value=1>
  12. <img src="captcha.php"><br>
  13. Введите изображенный на рисунке текст: <input type="text" name="capt"><br>
  14. <input type="submit">
  15. </form>

Во второй строке осуществляется запуск механизма сессий. Данная команда необходима для доступа к переменной $_SESSION['CaptchaKey'].

Обратите внимание на 4ю строку - в ней происходит проверка присвоено ли вообще какое-то значение переменной в сессии. Дело в том, что для того, чтобы сервер “узнал” браузер посетителя и мог сопоставить ему сессию, в которой хранится нужная нам переменная CaptchaKey, ему необходимо получить от браузера идентификатор сессии, который обычно передается через куки. Поэтому, если cookie отключены, то механизм сессий может оказаться нерабочим. В результате если ничего не вводить в форму, то равенство $_POST['capt']==$_SESSION['CaptchaKey'] окажется верным.

Теперь рассмотрим основной скрипт, за которым и скрывается вся основная работа. Вот его полный код:

  1. <?php
  2. define(PATH,str_replace(‘captcha.php’,‘fonts/’,__FILE__));
  3.  
  4. $alphabet="0123456789abcdefghijklmnopqrstuvwxyz";
  5. $s="23456789abcdeghkmnpqsuvxyz"; #alphabet without similar symbols (o=0, 1=l, i=j, t=f)
  6. $alphabet_length=strlen($alphabet);
  7. do {
  8. // generating random keystring
  9. $length=mt_rand(5,6);
  10. while(true) {
  11. $CaptchaKey=;
  12. for($i=0;$i<$length;$i++) $CaptchaKey.=$s{mt_rand(0,strlen($s)-1)};
  13. if(!preg_match(‘/cp|cb|ck|c6|c9|rn|rm|mm|co|do|cl|db|qp|qb|dp|ww/’,$CaptchaKey)) break;
  14. }
  15. session_register($CaptchaKey);
  16. $_SESSION[‘CaptchaKey’]=$CaptchaKey;
  17. $fonts=array();
  18. if($handle=opendir(PATH)) {
  19. while(false!==($file=readdir($handle)))
  20. if(preg_match(‘/.png$/i’,$file)) $fonts[]=PATH.$file;
  21. closedir($handle);
  22. }
  23. $s=$fonts[mt_rand(0,count($fonts)-1)];
  24. $font=imagecreatefrompng($s);
  25. imagealphablending($font,true);
  26. $fontfile_width=imagesx($font);
  27. $fontfile_height=imagesy($font)-1;
  28. $font_metrics=array();
  29. $symbol=0;
  30. $reading_symbol=false;
  31. // loading font
  32. for($i=0;$i<$fontfile_width &amp;&amp; $symbol<$alphabet_length;$i++) {
  33. $transparent=(imagecolorat($font,$i,0)>>24)==127;
  34. if(!$reading_symbol &amp;&amp; !$transparent) {
  35. $font_metrics[$alphabet{$symbol}]=array(’start’=>$i);
  36. $reading_symbol=true;
  37. continue;
  38. }
  39. if($reading_symbol &amp;&amp; $transparent){
  40. $font_metrics[$alphabet{$symbol}][‘end’]=$i;
  41. $reading_symbol=false;
  42. $symbol++;
  43. continue;
  44. }
  45. }
  46.  
  47. $width=120;
  48. $height=60;
  49. $img=imagecreatetruecolor($width,$height);
  50. imagealphablending($img,true);
  51. $white=imagecolorallocate($img,255,255,255);
  52. $black=imagecolorallocate($img,0,0,0);
  53. imagefilledrectangle($img,0,0,$width-1,$height-1,$white);
  54. //draw text
  55. $x=1;
  56. for($i=0;$i<$length;$i++) {
  57. $m=$font_metrics[$CaptchaKey{$i}];
  58. $y=mt_rand(-$fluctuation_amplitude, $fluctuation_amplitude)+($height-$fontfile_height)/2+2;
  59. $shift=0;
  60. if($i>0) {
  61. $shift=10000;
  62. for($sy=7;$sy<$fontfile_height-20;$sy+=1) {
  63. for($sx=$m[’start’]-1;$sx<$m[‘end’];$sx+=1) {
  64. $rgb=imagecolorat($font,$sx,$sy);
  65. $opacity=$rgb>>24;
  66. if($opacity<127) {
  67. $left=$sx-$m[’start’]+$x;
  68. $py=$sy+$y;
  69. if($py>$height) break;
  70. for($px=min($left,$width-1);$px>$left-12 &amp;&amp; $px>=0;$px-=1) {
  71. $color=imagecolorat($img, $px, $py) &amp; 0xff;
  72. if($color+$opacity<190) {
  73. if($shift>$left-$px) $shift=$left-$px;
  74. break;
  75. }
  76. }
  77. break;
  78. }
  79. }
  80. }
  81. if($shift==10000) $shift=mt_rand(4,6);
  82. }
  83. imagecopy($img,$font,$x-$shift,$y,$m[’start’],1,$m[‘end’]-$m[’start’],$fontfile_height);
  84. $x+=$m[‘end’]-$m[’start’]-$shift;
  85. }
  86. } while($x>=$width-10); // while not fit in canvas
  87.  
  88. $center=$x/2;
  89. $foreground_color = array(mt_rand(0,100), mt_rand(0,100), mt_rand(0,100));
  90. $background_color = array(mt_rand(200,255), mt_rand(200,255), mt_rand(200,255));
  91. $img2=imagecreatetruecolor($width,$height);
  92. $foreground=imagecolorallocate($img2, $foreground_color[0], $foreground_color[1], $foreground_color[2]);
  93. $background=imagecolorallocate($img2, $background_color[0], $background_color[1], $background_color[2]);
  94. imagefilledrectangle($img2, 0, 0, $width-1, $height-1, $background);
  95. imagefilledrectangle($img2, 0, $height, $width-1, $height+12, $foreground);
  96.  
  97. // periods
  98. $rand1=mt_rand(750000,1200000)/10000000;
  99. $rand2=mt_rand(750000,1200000)/10000000;
  100. $rand3=mt_rand(750000,1200000)/10000000;
  101. $rand4=mt_rand(750000,1200000)/10000000;
  102. // phases
  103. $rand5=mt_rand(0,31415926)/10000000;
  104. $rand6=mt_rand(0,31415926)/10000000;
  105. $rand7=mt_rand(0,31415926)/10000000;
  106. $rand8=mt_rand(0,31415926)/10000000;
  107. // amplitudes
  108. $rand9=mt_rand(330,420)/110;
  109. $rand10=mt_rand(330,450)/110;
  110. //wave distortion
  111. for($x=0;$x<$width;$x++) {
  112. for($y=0;$y<$height;$y++) {
  113. $sx=$x+(sin($x*$rand1+$rand5)+sin($y*$rand3+$rand6))*$rand9-$width/2+$center+1;
  114. $sy=$y+(sin($x*$rand2+$rand7)+sin($y*$rand4+$rand8))*$rand10;
  115. if($sx<0 || $sy<0 || $sx>=$width-1 || $sy>=$height-1){
  116. continue;
  117. }else{
  118. $color=imagecolorat($img, $sx, $sy) &amp; 0xFF;
  119. $color_x=imagecolorat($img, $sx+1, $sy) &amp; 0xFF;
  120. $color_y=imagecolorat($img, $sx, $sy+1) &amp; 0xFF;
  121. $color_xy=imagecolorat($img, $sx+1, $sy+1) &amp; 0xFF;
  122. }
  123. if($color==255 &amp;&amp; $color_x==255 &amp;&amp; $color_y==255 &amp;&amp; $color_xy==255){
  124. continue;
  125. }else if($color==0 &amp;&amp; $color_x==0 &amp;&amp; $color_y==0 &amp;&amp; $color_xy==0){
  126. $newred=$foreground_color[0];
  127. $newgreen=$foreground_color[1];
  128. $newblue=$foreground_color[2];
  129. }else{
  130. $frsx=$sx-floor($sx);
  131. $frsy=$sy-floor($sy);
  132. $frsx1=1-$frsx;
  133. $frsy1=1-$frsy;
  134. $newcolor=($color*$frsx1*$frsy1+$color_x*$frsx*$frsy1+$color_y
  135. *$frsx1*$frsy+$color_xy*$frsx*$frsy);
  136. if($newcolor>255) $newcolor=255;
  137. $newcolor=$newcolor/255;
  138. $newcolor0=1-$newcolor;
  139. $newred=$newcolor0*$foreground_color[0]+$newcolor*$background_color[0];
  140. $newgreen=$newcolor0*$foreground_color[1]+$newcolor*$background_color[1];
  141. $newblue=$newcolor0*$foreground_color[2]+$newcolor*$background_color[2];
  142. }
  143. imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newred, $newgreen, $newblue));
  144. }
  145. }
  146.  
  147. header(‘Expires: Mon, 26 Jul 1997 05:00:00 GMT’);
  148. header(‘Cache-Control: no-store, no-cache, must-revalidate’);
  149. header(‘Cache-Control: post-check=0, pre-check=0′, FALSE);
  150. header(‘Pragma: no-cache’);
  151. if(function_exists("imagejpeg")) {
  152. header("Content-Type: image/jpeg");
  153. imagejpeg($img2,null,90);
  154. } else if(function_exists("imagegif")) {
  155. header("Content-Type: image/gif");
  156. imagegif($img2);
  157. } else if(function_exists("imagepng")) {
  158. header("Content-Type: image/x-png");
  159. imagepng($img2);
  160. }
  161. ?>

В строках 2-6 происходит инициализация переменных, необходимых для дальнейшей работы скрипта.
В строках 8-14 происходит генерирование случайного кода каптчи (5 или 6 символов). При этом код проверяется на содержание неоднозначных символов (например, единицы и прописной L).
После того, как код сформирован, он помещается в сессию (строки 15-16).
Далее сканируется директорий /fonts и все найденные файлы с расширением .png помещаются в массив $fonst (строки 17-22), а в 22й строке выбирается (случайно) один из элементов этого массива.
Команда $font=imagecreatefrompng($s); создает PNG-изображение в оперативной памяти и назначает его идентификатор переменной $font. Команда imagealphablending сообщает, что при рисовании на изображении $font, цвет рисуемого пиксела будет получаться из совмещаения со цветом пиксела фона.
В строках 28-35 осуществляется формирование двумерного массива $font_metrics, содержащего координаты по оси X начала (”start”) и конца (”end”) каждого символа. Данная информация будет использоваться в дальнейшем при наложении букв.
Далее задается размер генерируемого изображения (120×60), происходит создание цвета “белый” и “черный” (строки 51-52) и заполнение рисунка $img, в котором, в последствии, будет рисоваться код каптчи.
В строках 55-85 происходит наложение частей изображения $fonts, соответствующих символам (посимвольно) кода каптчи на результатирующее изображение $img.
В строках 88-95 происходит следующее: сначала создаются случайные цвета $foreground и $background и создается новое изображение $img2. Далее на этом изображении вырисовывается прямоугольник. Теперь $img2 готов к тому, чтобы на него был наложен $img. Такие махинации необходимы для того, чтобы в строках 97-154 осуществить искривление изображения с целью сделать невозможным автоматическое распознование каптчи. За отсутствием необходимости подробно разбирать механизм искажения изображения не будем.
Далее, в строках 157-160 формируется и выдается в браузер заголовок html-страницы (этот заголовок должен быть послан в браузер до того, как на экран будет выведен какой-либо текст). Этот заголовок сообщает, что речь идет о рисунке, который запрещено кэшировать.
Ну и наконец строки 161-170 проверяют существование функций вывода изображения и выводят изображение в браузер в “сыром” виде (content-type: image).

Готовый скрипт проверки каптчи

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