Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Защита Web-форм от автоматической обработки

© Mike, 10.10.2004

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

Требования: PHP>=4.0.6, GD >=2.0.

Исходные тексты можно скачать тут.

Данная статья написана по мотивам статьи Nathan Rohler "Security Images in PHP" опубликованной на сайте #Dev Shed 9 августа 2004 года. Вообще, с начала, меня посетила мысль ее перевода, но, во первых автор выбрал интересный, но не самый тривиальный вариант решения проблемы, а во вторых, мне бы вряд ли удалость сформулировать на русском языке такое обилие мыслей.

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

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

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

Генерация изображения

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

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

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

Я бы предложил следующий алгоритм:

  1. Создаем подложку (для этого можно использовать алгоритм построения фракталов)
  2. Добавляем помехи - несколько случайных линий, цвета основного текста.
  3. Выводим основной текст
  4. Самое интересное - увеличиваем изображение в неровное количество раз - например, в 1.7, в 1.6
  5. Уменьшаем изображение до оригинальных размеров

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

Если вам кажется, что рисовать фрактал слишком сложно, то можно нарисовать простую сетку.

Принцип работы механизма

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

Код:
session_start();
session_register("secret_number");

if (intval($_SESSION["secret_number"])<1000) {
    srand(doubleval(microtime()));
    $_SESSION["secret_number"]=rand(1000,9999);
    }

После того как случайный текст сгенерирован, необходимо вывести форму:

Код:
<form action="index.php" method="post">
Ваш E-Mail:<br>
<input type="text" name="email" value=""><br>
<br>
Введите код, который вы видите на картинке:<br>
<input type="text" name="secretcode" value=""><br>
<img src='code.php?<?=doubleval(microtime());?>'
 width=101 height=26 vspace=5>
<br><br>
<input type="submit">
</form>

Скрипт, обрабатывающий данные, отправленные при помощи формы, должен работать примерно следующим образом:

Код:
session_start();
session_register("secret_number");

if ($_SERVER["REQUEST_METHOD"]=="POST") {

    $error=0;
    if ($_POST["secretcode"]!=$_SESSION["secret_number"] ||
        intval($_POST["secretcode"])==0) $error=1;

    if ($error==0) {
        $_SESSION["secret_number"]=rand(1000,9999);

        // Выполняем необходимые действия с данными
        // ..
        print "Hello ".htmlspecialchars(StripSlashes($_POST["email"]));
        exit;
        }

    if ($error==1)
        print "<font color=red>Число с картинки введено неверно</font>";
    }

// Выводим форму повторно
// ...

Генерация изображения

Код:
<?
// Регистрируем переменную
session_start();
session_register("secret_number");

function mt() {
    list($usec, $sec) = explode(' ', microtime());
    return (float) $sec + ((float) $usec * 100000);
    }

header("Content-type: image/png");

// создаем изображение
$im=imagecreate(101, 26);

// Выделяем цвет фона (белый)
$w=imagecolorallocate($im, 255, 255, 255);
 
// Выделяем цвет для фона (светло-серый)
$g1=imagecolorallocate($im, 192, 192, 192);

// Выделяем цвет для более темных помех (темно-серый)
$g2=imagecolorallocate($im, 64,64,64);

// Выделяем четыре случайных темных цвета для символов
$cl1=imagecolorallocate($im,rand(0,128),rand(0,128),rand(0,128));
$cl2=imagecolorallocate($im,rand(0,128),rand(0,128),rand(0,128));
$cl3=imagecolorallocate($im,rand(0,128),rand(0,128),rand(0,128));
$cl4=imagecolorallocate($im,rand(0,128),rand(0,128),rand(0,128));

// Рисуем сетку
for ($i=0;$i<=100;$i+=5) imageline($im,$i,0,$i,25,$g1);
for ($i=0;$i<=25;$i+=5) imageline($im,0,$i,100,$i,$g1);

// Выводим каждую цифру по отдельности, немного смещая случайным образом
imagestring($im, 5, 0+rand(0,10), 5+rand(-5,5),
    substr($_SESSION["secret_number"],0,1), $cl1);
imagestring($im, 5, 25+rand(-10,10), 5+rand(-5,5),
    substr($_SESSION["secret_number"],1,1), $cl2);
imagestring($im, 5, 50+rand(-10,10), 5+rand(-5,5),
    substr($_SESSION["secret_number"],2,1), $cl3);
imagestring($im, 5, 75+rand(-10,10), 5+rand(-5,5),
    substr($_SESSION["secret_number"],3,1), $cl4);

// Выводим пару случайных линий тесного цвета, прямо поверх символов.
// Для увеличения количества линий можно увеличить,
// изменив число выделенное красным цветом
for ($i=0;$i<8;$i++)
    imageline($im,rand(0,100),rand(0,25),rand(0,100),rand(0,25),$g2);


// Коэффициент увеличения/уменьшения картинки
$k=1.7;

// Создаем новое изображение, увеличенного размера
$im1=imagecreatetruecolor(101*$k,26*$k);

// Копируем изображение с изменением размеров в большую сторону
imagecopyresized($im1, $im, 0, 0, 0, 0, 101*$k, 26*$k, 101, 26);

// Создаем новое изображение, нормального размера
$im2=imagecreatetruecolor(101,26);

// Копируем изображение с изменением размеров в меньшую сторону
imagecopyresampled($im2, $im1, 0, 0, 0, 0, 101, 26, 101*$k, 26*$k);

// Генерируем изображение
imagepng($im2);

// Освобождаем память
imagedestroy($im2);
imagedestroy($im1);
imagedestroy($im);
?>

Вроде все. Если что-то, оставляйте комментарии.

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 

Комментарии

1.
45K
19 ноября 2008 года
vitalvarna
0 / / 19.11.2008
Мне нравитсяМне не нравится
19 ноября 2008, 09:22:09
У меня такая проблема: файл - обработчик у меня другой, не index.php, все работает отлично, но если пользователь не правильно ввел цифры, после нажатия кнопки НАЗАД(имею ввиду из файла обработчика), пользователь возвращается на страницу, где находится форма обратной связи, а эта форма уже пуста! Пользователю надо опять набирать свой эмаил, текст сообщения и т.д. И как поставить кнопку для обновления кода, без обновления страницы и формы?
2.
38K
29 марта 2008 года
Lloid69
0 / / 29.03.2008
Мне нравитсяМне не нравится
29 марта 2008, 14:26:17
Мой вариант:
<?php
session_start();

header("Content-type: image/png");

$image = imagecreate(128,64);

$background_color = ImageColorAllocate($image, rand(64,128), rand(64,128), rand(64,128));

$color1 = ImageColorAllocate($image, rand(128,255), rand(128,255), rand(128,255));
$color2 = ImageColorAllocate($image, rand(128,255), rand(128,255), rand(128,255));
$color3 = ImageColorAllocate($image, rand(128,255), rand(128,255), rand(128,255));

imagestring($image,5,40,35,$_SESSION["captcha"],$color1);

imageline($image, rand(0,128), rand(0,64), rand(0,128), rand(0,64),$color1);
imageline($image, rand(0,128), rand(0,64), rand(0,128), rand(0,64),$color2);
imageline($image, rand(0,128), rand(0,64), rand(0,128), rand(0,64),$color3);

imagesetpixel($image,rand(0,128),rand(0,64),$color1);
imagesetpixel($image,rand(0,128),rand(0,64),$color2);
imagesetpixel($image,rand(0,128),rand(0,64),$color3);
imagesetpixel($image,rand(0,128),rand(0,64),$color1);
imagesetpixel($image,rand(0,128),rand(0,64),$color2);
imagesetpixel($image,rand(0,128),rand(0,64),$color3);
imagesetpixel($image,rand(0,128),rand(0,64),$color1);
imagesetpixel($image,rand(0,128),rand(0,64),$color2);
imagesetpixel($image,rand(0,128),rand(0,64),$color3);
imagesetpixel($image,rand(0,128),rand(0,64),$color1);

imagepng($image);

imagedestroy($image);
?>
3.
13K
11 апреля 2006 года
grem11n
31 / / 11.04.2006
Мне нравитсяМне не нравится
29 августа 2007, 17:41:22
одна из немногих статей в нете помагающая. Спасибо автору
4.
477
02 декабря 2006 года
Robinnovich
182 / / 02.12.2006
+20 / -0
Мне нравитсяМне не нравится
16 июня 2007, 20:53:04
чето у меня вобще непхпло немного переделал )
<?php
header("Content-type: image/png");

$arr = array('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','r','s','t','u','v','x','y','z',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','R','S','T','U','V','X','Y','Z',
'1','2','3','4','5','6','7','8','9','0');
for($i = 0; $i < 5; $i++){
$index = rand(0, count($arr) - 1);
$string .= $arr[$index];
}
$width=100;
$height=26;
$im=imagecreate($width, $height);
$w=imagecolorallocate($im, mt_rand(200,255), mt_rand(200,255), mt_rand(200,255));
$height_font = 12;
$angle = 0;
$font_file = "arial.ttf";
$box = imagettfbbox($height_font, $angle, $font_file, $string);
for ($i=0; $i<strlen($string); $i++){
$wd =(($width-$box[2]-(($width-$box[2])/2))*$i/2+rand(-5,5))+5;
imagettftext($im, $height_font, $angle, $wd, $height/1.5+rand(-3,3), imagecolorallocate($im,rand(0,128),rand(0,128),rand(0,128)), $font_file, substr($string, $i, 1));
}
for ($i=0;$i<8;$i++)
imageline($im, rand(0,100), rand(0,100), rand(0,100), rand(0,50), imagecolorallocate($im, mt_rand(200,255), mt_rand(200,255), mt_rand(200,255)));
$k=6;
$im1=imagecreatetruecolor($width*$k,$height*$k);
imagecopyresized($im1, $im, 0, 0, 0, 0, $width*$k, $height*$k, $width, $height);
$im2=imagecreatetruecolor($width, $height);
imagecopyresampled($im2, $im1, 0, 0, 0, 0, $width, $height, $width*$k, $height*$k);
imagepng($im2);
imagedestroy($im2);
imagedestroy($im1);
imagedestroy($im);
?>
5.
24K
05 февраля 2007 года
tar
10 / / 05.02.2007
+20 / -0
Мне нравитсяМне не нравится
5 февраля 2007, 20:00:11
Почему только в IE и при загрузке из фрейма не отображаются
цифры ?
6.
24K
05 февраля 2007 года
tar
10 / / 05.02.2007
Мне нравитсяМне не нравится
5 февраля 2007, 19:58:59
Почему только в IE и при загрузке из фрейма не отображаются цифры
7.
13
04 июля 2006 года
RussianSpy
3.0K / / 04.07.2006
Мне нравитсяМне не нравится
29 ноября 2006, 11:58:02
Интересно... А что же будет с теми у кого отключены куки? Получается что они не смогут работать с подобной схемой. Не стоит делать защитную картинку работа которой основана на сессиях. Да и вообще код как-то великоват - класс который я сам написал работает без сессий и там раз в 5 меньше кода...

Моя оценка: 3 с плюсом
8.
21K
07 сентября 2006 года
Style)r
0 / / 07.09.2006
Мне нравитсяМне не нравится
9 октября 2006, 22:49:26

>>Это бред чистой воды :)
Але працює :)
9.
4
01 октября 2002 года
mike
3.7K / / 01.10.2002
Мне нравитсяМне не нравится
8 сентября 2006, 11:07:44
Основная идея - не менять картинку для одного пользователя в момент обновления. Менять только в момент добавления.

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

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

Цитата:

srand(doubleval(microtime()))

змінити на

srand(date("s")); (наприклад)



Это бред чистой воды :) От замены микрсекунд на секунды ничего не поменяется. Просто внутри одной секунды генератор случайных чисел будет инициализироваться одинаково.

10.
21K
07 сентября 2006 года
Style)r
0 / / 07.09.2006
Мне нравитсяМне не нравится
7 сентября 2006, 18:28:12
Я заради вас навіть зареєструвався тут)))
Щоб картинка змінювалася (изменялась)!!! Треба (нужно строку) рядок
srand(doubleval(microtime()))

змінити на

srand(date("s")); (наприклад)

І все буде окей )))
11.
19K
27 июня 2006 года
HerSystem
0 / / 27.06.2006
Мне нравитсяМне не нравится
27 июня 2006, 11:38:38
И в форме уже просто:
<form action="index.php" method="post">
Введите код, который вы видите на картинке:<br>
<input type="text" name="secretcode"><br>
<img src='code.php'><br>
<input type="submit">
</form>
12.
19K
27 июня 2006 года
HerSystem
0 / / 27.06.2006
Мне нравитсяМне не нравится
27 июня 2006, 11:25:50
Чёто мне кажется нагрузили тут лишнего! + функциии, которые в php4-5 уже не нужны! Может я и ошибаюсь.. Вот что я сделал(точнее сократил) + при нажатии F5 цифры меняются:
Из code.php удаляем:
session_register("secret_number");
function mt() {
list($usec, $sec) = explode(' ', microtime());
return (float) $sec + ((float) $usec * 100000);
}

А в index.php заменяем весь код проверки и генерации на:
session_start();
if ($_POST)
{
if($_POST["secretcode"] == $_SESSION["secret_number"])
{
echo "Код принят";
exit;
}
else {echo "Вы ввели неправильно номер на картинке";}
} else {$_SESSION["secret_number"] = rand(1000,9999);}
//А дальше форму

Что я не правильно сделал?
13.
4
01 октября 2002 года
mike
3.7K / / 01.10.2002
Мне нравитсяМне не нравится
13 июня 2006, 23:49:57

Цитата:

Что-бы избежать отображения одного и того-же кода при обновлении страницы (F5).

А чем плохо то что код во время обновления страницы не меняется ?

14.
11K
22 ноября 2005 года
serijvolk
20 / / 22.11.2005
Мне нравитсяМне не нравится
13 июня 2006, 20:14:24
Что-бы избежать отображения одного и того-же кода при обновлении страницы (F5).

if ($_SERVER["REQUEST_METHOD"]=="POST") {
$error=0;
if ($_POST["secretcode"]!=$_SESSION["secret_number"] || intval($_POST["secretcode"])==0) $error=1;

if ($error==0) {
$_SESSION["secret_number"]=rand(1000,9999);

// Выполняем необходимые действия с данными
// ..
print "Hello ".htmlspecialchars(StripSlashes($_POST["email"]));
exit;
}

if ($error==1) print "<font color=red>Число с картинки введено неверно</font>";
}

srand(doubleval(microtime()));
$_SESSION["secret_number"]=rand(1000,9999);
// Выводим форму повторно
// ...
15.
Аноним
+0 / -20
Мне нравитсяМне не нравится
2 марта 2006, 20:32:03
Народ всегда будет один код но когда вы ведеты его верно код сменится на следущий раз =)!!!
16.
Аноним
+20 / -0
Мне нравитсяМне не нравится
3 февраля 2006, 14:12:56
Я использую ваш код и
почему-то всё часто выводит число 8561 и на всех компьютерах также
17.
Аноним
Мне нравитсяМне не нравится
20 января 2006, 20:19:59
<blockquote><small>Цитата:<hr size=1>у меня должно быть
<form action="send.php" method="post">

а скрипт требует что в форме стоял
<form action="index.php" method="post">[/quote]Так и нужно передавать в send.php. А уже в него встраивать проверку верности числа.
18.
Аноним
Мне нравитсяМне не нравится
20 января 2006, 20:19:12
<blockquote><small>Цитата:<hr size=1>Число на картинке реально одно и то же всегда :(
Даже тут, в комментариях... Как быть? :([/quote]Оно и должно быть всегда одно и то же. Изменится оно только после добавления комментария. У других пользователей он другое, и тоже изменяется только при добавлении комментария.
19.
Аноним
Мне нравитсяМне не нравится
20 января 2006, 17:42:33
Привет всем.

Работает замечательно. Но я его не как не могу совместить со с моей формой.
Дело в том что у меня форма состоит из двух файлов
1. Сома форма (index.php) и
2. Обработчик (send.php)

Проблема состоит в том что когда я вставляю код со скрипта в
мою форму, получаетца противаречие.
у меня должно быть
<form action="send.php" method="post">

а скрипт требует что в форме стоял
<form action="index.php" method="post">

А как же данные передавать в (send.php)?
Джентелмены подскажыте как решыть эту зодачу.

Гарик.

20.
Аноним
Мне нравитсяМне не нравится
2 января 2006, 15:14:24
Сорки за пред. сообщение. Что-то многовато символово можно вставлять. Вам так не кажется? По теме: число на картинке реально одно и то же всегда :(
Даже тут, в комментариях... Как быть? :(
21.
Аноним
Мне нравитсяМне не нравится
18 декабря 2005, 21:55:48
Эхх,я таки не смог вставить это к себе на сайтик,как ни стрался постоянно выводилось 4071,
эххх(((
22.
Аноним
Мне нравитсяМне не нравится
3 августа 2005, 08:59:54
А вот и ссылка:
http://www.xakep.ru/magazine/xa/073/120/1.asp
23.
Аноним
Мне нравитсяМне не нравится
22 июля 2005, 12:18:07
<blockquote><small>Цитата:<hr size=1>
На сегодняшний день мне не знакомо ни одной программы, способной обойти такую защиту. Я даже на знаю ни одной программы, вообще, хоть как-то пытающуюся распознать содержимое картинки.
[/quote]
Такие программы есть, и их очень легко сделать самому. В каком то из номеров журнала "Хакер" написали как это сделать на Perle.
Именно таким образом и поломали защиту у "пчеловодов"(BeeLine).
24.
Аноним
Мне нравитсяМне не нравится
17 июля 2005, 02:31:47
Вряд-ли. Т.к. некоторые программы могут иметь преднастраиваемый интерфейс, так что только ручное подтверждение гарантирует неавтоматизированность процесса ... Сам такие пишу ...
25.
Аноним
Мне нравитсяМне не нравится
22 мая 2005, 16:11:51
а как позключить GD >=2.0
26.
Аноним
Мне нравитсяМне не нравится
22 апреля 2005, 10:29:17
Думаю можно придумать более удобные способы защиты кроме как с помощью изображений.
Программы автозаполнения обычно сканируют страницу на стандартные имена полей (<input name="email"...), которые редко отличаются названиями даже на разных сайтах.
Что если генерировать не текст на изображении, а название поля?
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог