From: Vladimir Maximenko <4raznoe@mail.ru>
Newsgroups: email
Date: Mon, 12 Jul 2004 18:21:07 +0000 (UTC)
Subject: Реализация теста Тьюринга на Perl (ввод цифр изображенных на картинке)
"Введите число, изображенное на картинке"
В статье описан метод защиты от автоматического заполнения и отправки
формы с сайта путем динамической генерации картинки с кодом и
подтверждения правильности ввода.
Как это работает
----------------
Когда происходит заполнения полей с данными, система просит пользователя
указать код, который он видит на картинке.
После проверки правильности ввода происходит решение - пускать клиента в
систему или нет.
Большой плюс такого метода - надежность. "Вскрыть" картинку и найти в
ней цифры не так-то просто, потому что здесь придется писать сложный
анализатор изображения.
Алгоритм работы
---------------
- генерируем случайное число с заданным количеством разрядов или слово
из словаря (будет использоваться как надпись на картинке) - пароль
- создаем случайный код сессии
- во временном каталоге создаем файл с именем сессии, внутри содержится
сгенерированный пароль
- не забываем удалять старые файлы сессий, время жизни которых
истекло - неудачные попытки авторизации
- в заполняемой пользователем форме вставляем hidden поле с кодом
сессии и поле ввода пароля
- генерируем и показываем на странице подготовленную картинку с паролем
(делаем ее трудночитаемой для возможных анализаторов, но понятной
человеку)
- после отправки заполненной формы сравниваем содержимое файла сессии с
введенным паролем, если значения совпадают - значит форму заполняет
человек, вносим данные
- удаляем файл завершившейся сессии
Замечания по реализации
-----------------------
Сначала была мысль не использовать файлы сессий, а передавать в форме в
hidden поле зашифрованный по MD5 пароль или обойтись просто созданием
временных файлов с именами-значениями пароля, и проверять только их
наличие.
Не забываем раскручивать генератор случайных чисел.
В примере пароль создается исключительно из цифр, но для повышения
безопасности можно добавить и буквы
@pass_chars=("A".."Z", "a".."z", 0..9, qw(% ! $ % ^ & *));
Также можно использовать слова и куски текста из словаря.
Шифрованный код сессии
Используется модуль Perl Digest::MD5 http://search.cpan.org/dist/Digest-MD5/
Уникальная строка для шифрования - текущее время в raw-формате, а также
процесс скрипта.
$salt=Digest::MD5->new;
$hash = $salt->add(time().$$);
# шифруем методом ASCII-HEX
$session_code=$hash->hexdigest;
Подготовленная картинка с паролем
Картинка отображается на странице как STDOUT работы небольшого скрипта,
генерирующего картинку, код сессии передаем скрипту как параметр
<img align="right"
src="/cgi-bin/anti_robot_img.cgi?code=<session_code>" border=1 alt="">
Если параметр не задан - генерируется картинка со случаным паролем.
Для создания и вывода картинки используется модуль Image::Magick http://www.imagemagick.org/
Слово пароля посимвольно выводим на изображение, каждая буква
отображается со случайным сдвигом по горизонтали и вертикали, а также
вращением.
После этого "зашумляем" изображение - сверху в случайных местах
разбрасываем разноцветные точки.
Можно еще добавить вывод букв разными шрифтами и цветами, а также
использовать разноцветный фон (например кусочки фотографий)
Исходники модуля можно взять по адресу http://voldemar.info/files/anti_robot_img.pm
(копия в конце статьи)
Система успешно применяется на сайте "ПРАЙСЫ online" в разделе "Доска
объявлений" http://www.price-list.kiev.ua/cgi-bin/msg_board.cgi?do=add-msg
Владимир Максименко - веб-мастер http://www.price-list.kiev.ua
E-Mail: 4raznoe@mail.ru
http://WWW.VOLDEMAR.INFO
12.07.2004
# anti_robot_img.pm
# введи число, изображенное на картинке
# защита от заполнения формы с данными роботами
# Vladimir Maximenko 4raznoe@mail.ru
# строка случайных символов для картинки
sub get_pass_str
{
srand();
return join("", @pass_chars[map {rand @pass_chars}(1..$kol_digit)]);
}
# шифрованый код сессии
sub enctypt_session_code
{
my $salt=Digest::MD5->new;
# уникальная строка для шифрования
my $hash = $salt->add(time().$$);
# шифруем методом ASCII-HEX
return $hash->hexdigest;
}
# удаляем старые сессии
sub del_old_session
{
opendir (DIR, $flag_dir);
my @flag_files=readdir (DIR);
closedir (DIR);
shift(@flag_files);shift(@flag_files);
foreach $f (@flag_files)
{
if ($f =~ /.anti_robot$/)
{
my $mtime=(stat("$flag_dir/$f"))[9];
if ($mtime<time()-$time_flag_live) { unlink("$flag_dir/$f") || die "$!"; }
}
}
}
# проверяем, правильно ли введен проверочный код
sub check_anti_robot
{
my ($pass_str, $session_code)=@_;
# была такая сессиия ?
if (-e "$flag_dir/$session_code.anti_robot")
{
# правильный пароль ввели ?
$check_pass="";
open(FILE,"$flag_dir/$session_code.anti_robot") || die "$!";
while (<FILE>) { $check_pass.=$_; }
close(FILE);
# новая сессия, готовим ловушку для робота
sub anti_new_session
{
# новые случайные значения
my $new_pass_str=&get_pass_str();
my $new_session_code=&enctypt_session_code();
# удаляем старые сессии
&del_old_session();
# пишем в проверочный файл
open(FILE,">$flag_dir/$new_session_code.anti_robot") || die "$!";
print FILE $new_pass_str;
close(FILE);
return $new_pass_str, $new_session_code;
}
# выводим подготовленную картинку с паролем
sub get_pass_img
{
# из номера сессии достаем пароль
if ($_[0] and -e "$flag_dir/$_[0].anti_robot")
{
$pass_str="";
open(FILE,"$flag_dir/$_[0].anti_robot") || die "$!";
while (<FILE>) { $pass_str.=$_; }
close(FILE);
}
else { $pass_str=&get_pass_str(); }
# новая картинка
$image = Image::Magick->new;
$image->Set(size=>$img_width."x".$img_height);
$image->ReadImage('xc:white');
# выводим пароль с искажением текста
my $step_x=15;
my $base_x=5;
my $base_y=$img_height-5;
foreach my $letter (split(//, $pass_str))
{
# сдвиг и вращение буквы
my $sdvig_x=int(rand(4))-2;
my $sdvig_y=int(rand(10))-5;
my $rotate=int(rand(60))-30;
$image->Annotate(
antialias => 'true',
pointsize => 22,
x => $base_x+$sdvig_x,
y => $base_y+$sdvig_y,
rotate => $rotate,
fill => 'black',
encoding=> 'windows1251',
text => $letter
);
$base_x+=$step_x;
}
# добавляем шум (для затруднения восприятия)
for my $i (1..$noise_pixels)
{
my $rnd_x=int(rand($img_width));
my $rnd_y=int(rand($img_height));
$image->Set("pixel[$rnd_x,$rnd_y]"=>"red");
}