CodeNet / Языки программирования / PHP / HTML, формы
CodeNet / Языки программирования / PHP / Безопасность
Защита Web-форм от автоматической обработки
Статья описывает способ реализации механизма, позволяющего бороться с автоматическим заполнением форм, ложными регистрациями и спамом через форму обратной связи.
Требования: PHP>=4.0.6, GD >=2.0.
Исходные тексты можно скачать тут.
Данная статья написана по мотивам статьи Nathan Rohler "Security Images in PHP" опубликованной на сайте #Dev Shed 9 августа 2004 года. Вообще, с начала, меня посетила мысль ее перевода, но, во первых автор выбрал интересный, но не самый тривиальный вариант решения проблемы, а во вторых, мне бы вряд ли удалость сформулировать на русском языке такое обилие мыслей.
Последнее время, в связи с распространяющейся эпидемией спама, веб-мастера, стали все чаще и чаще прятать адреса своей электронной почты (E-Mail). Многие стали использовать формы обратной связи. Но как оказалось, такую защиту можно обойти. И уже на сегодняшний день, существует огромное количество программ, предназначенных для рассылки спама, через формы обратной связи.
Наша задача - сделать так, чтобы сообщение вам смог отправить только "живой человек". Чаще всего, для этого используют небольшие картинки, на которых выводится текст. Пользователя просят продублировать этот текст в поле ввода. Если дублирование производится неверно, то форма не обрабатывается.
На сегодняшний день мне не знакомо ни одной программы, способной обойти такую защиту. Я даже на знаю ни одной программы, вообще, хоть как-то пытающуюся распознать содержимое картинки. По этому, можно смело сказать, что сегодня нет необходимости как-то искажать изображение на картинке. Но мы смотрим в будущее.
Генерация изображения
Автор предложил следующий алгоритм: сложное изображение на картинке формируется с помощью заранее подготовленных подложек. После чего на подложки выводится текст, случайным шрифтом, случайного размер и, естественно, случайного содержания.
Основной недостаток такого алгоритма заключается в том, что существующую "подложку" можно быстро вычислить. А после этого вычесть из изображения, которое необходимо распознать и таким образом получить картинку с чистым текстом. А для распознания такого текста уже сегодня существует масса программ.
Наша задача - сделать абсолютно случайную подложку, с текстом, который не то что распознать сложно, его прочитать тяжело.
Я бы предложил следующий алгоритм:
- Создаем подложку (для этого можно использовать алгоритм построения фракталов)
- Добавляем помехи - несколько случайных линий, цвета основного текста.
- Выводим основной текст
- Самое интересное - увеличиваем изображение в неровное количество раз - например, в 1.7, в 1.6
- Уменьшаем изображение до оригинальных размеров
Увеличивать и уменьшать изображение необходимо с использованием сглаживания, иначе даже человек не сможет прочитать текста.
Если вам кажется, что рисовать фрактал слишком сложно, то можно нарисовать простую сетку.
Принцип работы механизма
При заходе пользователя на страницу с формой, мы создаем сессию и записываем в зарегистрированную переменную случайный код:
session_register("secret_number");
if (intval($_SESSION["secret_number"])<1000) {
srand(doubleval(microtime()));
$_SESSION["secret_number"]=rand(1000,9999);
}
После того как случайный текст сгенерирован, необходимо вывести форму:
Ваш 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_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);
?>
Вроде все. Если что-то, оставляйте комментарии.
Оставить комментарий
Комментарии
<?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);
?>
<?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);
?>
цифры ?
Моя оценка: 3 с плюсом
>>Это бред чистой воды :)
Але працює :)
Пользователю намного удобнее, он не должен каждый раз вчитываться в код на картинке, если форма по той или иной причине не обработана.
Код меняется только с случае успешного заполнения формы.
srand(doubleval(microtime()))
змінити на
srand(date("s")); (наприклад)
Это бред чистой воды :) От замены микрсекунд на секунды ничего не поменяется. Просто внутри одной секунды генератор случайных чисел будет инициализироваться одинаково.
Щоб картинка змінювалася (изменялась)!!! Треба (нужно строку) рядок
srand(doubleval(microtime()))
змінити на
srand(date("s")); (наприклад)
І все буде окей )))
<form action="index.php" method="post">
Введите код, который вы видите на картинке:<br>
<input type="text" name="secretcode"><br>
<img src='code.php'><br>
<input type="submit">
</form>
Из 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);}
//А дальше форму
Что я не правильно сделал?
Что-бы избежать отображения одного и того-же кода при обновлении страницы (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);
// Выводим форму повторно
// ...
почему-то всё часто выводит число 8561 и на всех компьютерах также
<form action="send.php" method="post">
а скрипт требует что в форме стоял
<form action="index.php" method="post">[/quote]Так и нужно передавать в send.php. А уже в него встраивать проверку верности числа.
Даже тут, в комментариях... Как быть? :([/quote]Оно и должно быть всегда одно и то же. Изменится оно только после добавления комментария. У других пользователей он другое, и тоже изменяется только при добавлении комментария.
Работает замечательно. Но я его не как не могу совместить со с моей формой.
Дело в том что у меня форма состоит из двух файлов
1. Сома форма (index.php) и
2. Обработчик (send.php)
Проблема состоит в том что когда я вставляю код со скрипта в
мою форму, получаетца противаречие.
у меня должно быть
<form action="send.php" method="post">
а скрипт требует что в форме стоял
<form action="index.php" method="post">
А как же данные передавать в (send.php)?
Джентелмены подскажыте как решыть эту зодачу.
Гарик.
Даже тут, в комментариях... Как быть? :(
эххх(((
http://www.xakep.ru/magazine/xa/073/120/1.asp
На сегодняшний день мне не знакомо ни одной программы, способной обойти такую защиту. Я даже на знаю ни одной программы, вообще, хоть как-то пытающуюся распознать содержимое картинки.
[/quote]
Такие программы есть, и их очень легко сделать самому. В каком то из номеров журнала "Хакер" написали как это сделать на Perle.
Именно таким образом и поломали защиту у "пчеловодов"(BeeLine).
Программы автозаполнения обычно сканируют страницу на стандартные имена полей (<input name="email"...), которые редко отличаются названиями даже на разных сайтах.
Что если генерировать не текст на изображении, а название поля?