ProfiPHPProfiPHP
Категория: Полезное в PHP

Защита от спама с помощью CAPTCHA

CAPTCHA расшифровывается как Completely Automated Public Turing test to tell Computers and Humans Apart, что в переводе означает "полностью автоматизированный публичный тест Тьюринга для различения компьютеров и людей". Этот термин очень часто произносят так, как он произносится по-английски — "капча".

Уже из названия понятно, что это тест, который должен гарантировать, что операцию выполняет именно человек, а не компьютер. В Интернете такая проверка очень актуальна. Дело в том, что злоумышленник может написать программу, которая будет отправлять флуд или голосовать вместо человека, накручивая счетчики программно. Хорошая проверка CAPTCHA гарантирует, что операцию выполнял именно человек, а не компьютер, а значит, взломщику придется прилагать собственные усилия, чтобы ее пройти.

Вам уже много раз встречался тест CAPTCHA на разных сайтах в виде:

- картинок с цифрами и/или буквами, которые нужно ввести;

- задания (чаще математические), которые нужно решить, вопросы, на которые нужно ответить.

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

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

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

Какой выбрать шум?

- можно покрыть цифры и фон точками, только вот точек для создания полноценного шума понадобится очень много. В таком случае придется использовать цикл, является ресурсозатратной задачей;

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

- можно нарисовать несколько линий на секретном коде. Достаточно пяти линий, чтобы изображение получилось зашумленных, но эффективность этого метода так же далека от идеала. Злоумышленник может написать сценарий, который будет распознавать линии на изображении и удалять их.

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

Итак, создадим файл "captcha.php" и с последующим содержанием:
session_start( ); 
header( "Content-type: image/png" );
$img = imagecreatetruecolor( 130, 24 ) or die( 'Cannot create image' );
imagefill( $img, 0, 0, 0xFFFFFF );
$x = 0;
$i = 1;
$sum = "";
while ( $i++ <= 5 ) {
imagettftext( $img,
rand( 14, 18 ),
rand( -12, 12 ),
$x = $x + 20,
15 + rand( 0, 5 ),
imagecolorallocate( $img, rand( 0, $i * 25 ), rand( 0, $i * 25 ), rand( 0, $i * 25 ) ),
"arial.ttf",
$rnd = rand( 0, 9 ) );
$sum = $sum.( string )$rnd;
$background = imagecolorallocate( $img, 255, 0, 0 );
imageline( $img, 0, rand( 0, 24 ), 130, rand( 0, 40 ), $background );
}
$_SESSION['secret_number'] = $sum;
imagepng( $img );
imagedestroy( $img );
Давайте рассмотрим, что здесь происходит. Сразу после запуска сеанса мы вызываем функцию Header. Эта функция должна вызываться к выводу какой бы то ни было информации, так как она задает заголовок следующих данных. Если вы не вызовете эту функцию, то будет создан заголовок по умолчанию, который соответствует HTML-странице. Наш сценарий будет создавать картинку, а не страницу, поэтому мы вызываем функцию Header и указываем в качестве параметра "Content-type: image/png". Данный тип заголовка определяет картинку в формате PNG, и именно ее мы должны сгенерировать в сценарии.

После этого вызывается функция Imagecreatetruecolor, которая создает картинку в памяти сервера. В качестве параметров передаются необходимые ширина и высота картинки. Сразу же заполняем всю область картинки белым фоном с помощью функции Imagefill.

Фон для рисования готов. Запускаем цикл из пяти шагов, на каждом шагу которого рисуем одну случайную цифру и линию со случайными координатами. В этом же цикле мы добавляем к переменной $sum нарисованную цифру. В этой переменной мы собираем сгенерированный код в одно целое.

Обратите внимание, что в сценарии используется шрифт "arial.ttf". Это шрифт из стандартной поставки Windows. Поэтому вы должны загрузить в директорию сценарию данный шрифт.

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

Посмотрим на сам сценарий формы, который показан в следующем листинге:
<?php session_start( ); ?> 
<html>
<head>
<title>Using captcha</title>
</head>
<body>
<?php
if ( $_SESSION['secret_number'] == "" )
{
$_SESSION['secret_number'] = "ABCD";
}
if ( isset( $_POST['comtext'] ) )
{
if ( $_SESSION['secret_number'] != $_POST['pkey'] )
{
echo 'Wrong secret number';
}
else
{
echo 'You are the best';
}
}
?>
<form action="" method="post">
<table>
<tr>
<td><p>Имя:</p></td>
<td><input name="username" value="" size="40" /></td>
</tr>
<tr>
<td><p>Текст:</p></td>
<td><textarea cols="30" name="comtext" rows="5" /></textarea></td>
</tr>
<tr>
<td><p>Код безопасности:</p></td>
<td><input name="pkey" value="" size="18"><img src="captcha.php" /></td>
</tr>
</table>
<p><input type="submit" value="Сохранить"></p>
</form>
</body>
В этом сценарии нам также нужно запустить сеанс, чтобы можно было видеть значение секретного кода. После этого идет PHP-код и HTML-код формы, которую видит пользователь. Чтобы отобразить картинку кода, мы используем тег "<img>", и вместо пути к изображению указываем путь к сценарию "captcha.php", написанный ранее:
<img src="captcha.php" />
При использовании теста CAPTCHA есть один нюанс - нельзя забывать, что переменная секретного кода ранее нигде не устанавливалась. Посмотрим, что произойдет, если мы просто напишем сравнения:
if ( $_SESSION[secret_number] ! = $_POST['pkey'] )
{
}
Здесь мы сравниваем значение секретного кода и сеанса с параметром, передаваемым от пользователя. В чем опасность? Если хакер сохранил страницу у себя на диске, чтобы отправить запрос со своего компьютера, и при этом удалил из сценария поле с защитным кодом и обращения к картинке, то у нас начнутся проблемы. После загрузки этой формы без картинки переменная в сеансе будет пуста. Если хакер отправит этот запрос на сервер и оставит параметр "pkey" пустым, обе части условия будут пустыми и результат проверки окажется положительным. То есть хакер легким движением руки обойдет нашу проверку CAPTCHA.

Данная уязвимость относится к классу уязвимостей, связанных с установкой значений по умолчанию. Так как значения по умолчанию в двух переменных одинаковые, задача хакера сводится к тому, чтобы удалить все лишнее.

Защита от такого обхода проста. Перед сравнением параметров нужно добавить следующую проверку:
if ( $_SESSION['secret_number'] == "" ) { 
$_SESSION['secret_number'] = "ABCD";
}
Все очень просто. Если сеансовая переменная пуста, то есть картинка раньше не вызывалась, необходимо сохранить в сеансе какой-нибудь шум, чтобы там не было пустого значения по умолчанию. Теперь хакеру будет трудно обойти проверку CAPTCHA. Нам еще остается принять меры, чтобы хакер не смог узнать наше значение по умолчанию. Если вы пишете открытый код, который будут видеть другие, то инициализировать переменную лучше случайным значением.

Добавить комментарий

Имя:
Текст комментария: